Compare commits

...
Author SHA1 Message Date
TrèsAbhi 22f42117df feat: more hot garbage 2025-05-26 18:30:00 -05:00
TrèsAbhi 788edf5bdf feat: more hot garbage 2025-05-26 18:29:29 -05:00
TrèsAbhi d0170d53e4 feat: hot garbage 2025-05-26 14:23:00 -05:00
TrèsAbhi ae5e38139b feat: some set info in meta section 2025-05-25 18:49:00 -05:00
TrèsAbhi e38713f365 feat: meta section wallpapers 2025-05-25 18:08:00 -05:00
TrèsAbhi 5903b8130b feat: some of meta section 2025-05-25 18:03:55 -05:00
TrèsAbhi 179ce62a55 feat: strings 2025-05-25 15:18:22 -05:00
TrèsAbhi a2f1593a0e feat: add accessors 2025-05-24 18:41:03 -05:00
TrèsAbhi 6305db7ad6 feat: update to correct branch 2025-05-24 12:28:57 -05:00
TrèsAbhi 5082e1b82b feat: new protos 2025-05-24 12:24:53 -05:00
89 changed files with 842 additions and 431 deletions

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ depots
public/sitemap* public/sitemap*
/firebase.json /firebase.json
target target
# packages/core/src/protos/*.ts # *.ts
/.vercel /.vercel
/.astro /.astro
.vercel .vercel
+6
View File
@@ -33,6 +33,7 @@
"blitzkit", "blitzkit",
"blitzrinth", "blitzrinth",
"blitzstars", "blitzstars",
"bufbuild",
"cacheable", "cacheable",
"Callouts", "Callouts",
"Camo", "Camo",
@@ -75,6 +76,7 @@
"dxgi", "dxgi",
"echarts", "echarts",
"Everytime", "Everytime",
"FABLEGEAR",
"FASTNAME", "FASTNAME",
"flexdirection", "flexdirection",
"FOURCC", "FOURCC",
@@ -108,6 +110,7 @@
"Maddoxkkm", "Maddoxkkm",
"malum", "malum",
"mantlets", "mantlets",
"MECHA",
"metallicness", "metallicness",
"metalness", "metalness",
"minitelrose", "minitelrose",
@@ -145,9 +148,11 @@
"POSITIVEY", "POSITIVEY",
"POSITIVEZ", "POSITIVEZ",
"precompute", "precompute",
"PROGRESSTON",
"Promisable", "Promisable",
"proto", "proto",
"protobuf", "protobuf",
"protoc",
"protos", "protos",
"Pyogenics", "Pyogenics",
"ratingleaderboard", "ratingleaderboard",
@@ -203,6 +208,7 @@
"vitepress", "vitepress",
"Vovko", "Vovko",
"wargaming", "wargaming",
"WARHAMMER",
"webp", "webp",
"whitespaces", "whitespaces",
"WHSS", "WHSS",
BIN
View File
Binary file not shown.
+11 -2
View File
@@ -1,12 +1,13 @@
{ {
"name": "blitzkit", "name": "@blitzkit",
"private": true, "private": true,
"version": "1.27.11", "version": "1.27.11",
"description": "🎉 Tools for everything World of Tanks Blitz", "description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module", "type": "module",
"types": "./types.d.ts", "types": "./types.d.ts",
"workspaces": [ "workspaces": [
"packages/*" "packages/*",
"submodules/*"
], ],
"scripts": { "scripts": {
"build:assets": "cd packages/scripts && bun run build:assets", "build:assets": "cd packages/scripts && bun run build:assets",
@@ -41,15 +42,23 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"googleapis": "^144.0.0", "googleapis": "^144.0.0",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"protobufjs": "^7.5.2", "protobufjs": "^7.5.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"quicklime": "^1.2.0", "quicklime": "^1.2.0",
"sharp": "^0.33.5" "sharp": "^0.33.5"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/react": "^4.3.0",
"@types/bun": "latest", "@types/bun": "latest",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/react": "^19.1.5",
"@types/react-dom": "^19.1.5",
"@types/node": "^22.14.1", "@types/node": "^22.14.1",
"astro": "^5.8.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prisma": "^6.6.0", "prisma": "^6.6.0",
"typescript": "^5.8.3" "typescript": "^5.8.3"
-1
View File
@@ -9,7 +9,6 @@
"satori": "^0.10.14", "satori": "^0.10.14",
"@resvg/resvg-js": "^2.6.2", "@resvg/resvg-js": "^2.6.2",
"discord.js": "^14.16.3", "discord.js": "^14.16.3",
"react": "^18.3.1",
"@radix-ui/colors": "^3.0.0", "@radix-ui/colors": "^3.0.0",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"bepaint": "^0.1.0-beta.3", "bepaint": "^0.1.0-beta.3",
+8 -1
View File
@@ -1,4 +1,11 @@
{ {
"extends": "../../tsconfig.json", "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
@@ -4,6 +4,7 @@
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"dependencies": { "dependencies": {
"@blitzkit/closed": "workspace:*",
"@octokit/plugin-throttling": "^8.2.0", "@octokit/plugin-throttling": "^8.2.0",
"@octokit/rest": "^20.1.1", "@octokit/rest": "^20.1.1",
"@prisma/client": "^6.6.0", "@prisma/client": "^6.6.0",
@@ -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<string, Any> = {};
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<Type>(Message: MessageFns<Type>, component: string) {
if (component in this.components) {
return Message.decode(this.components[component].value);
}
throw new Error(`Unknown component: ${component}`);
}
}
+4
View File
@@ -0,0 +1,4 @@
export function entityName(entity: string) {
const [_, ...name] = entity.split('.');
return name.join('.');
}
+3
View File
@@ -1,7 +1,9 @@
export * from './canSplash'; export * from './canSplash';
export * from './catalogItemAccessor';
export * from './clans'; export * from './clans';
export * from './classes'; export * from './classes';
export * from './constants'; export * from './constants';
export * from './entityName';
export * from './equipmentPrice'; export * from './equipmentPrice';
export * from './fetchBlitz'; export * from './fetchBlitz';
export * from './fetchGlossary'; export * from './fetchGlossary';
@@ -18,6 +20,7 @@ export * from './getTankStats';
export * from './idToRegion'; export * from './idToRegion';
export * from './isOnGoingRatingSeason'; export * from './isOnGoingRatingSeason';
export * from './isValidBlitzId'; export * from './isValidBlitzId';
export * from './metadataAccessor';
export * from './nations'; export * from './nations';
export * from './normalizeExtra'; export * from './normalizeExtra';
export * from './normalizeIds'; export * from './normalizeIds';
@@ -0,0 +1,38 @@
import { CatalogItem } from '@protos/blitz_items';
import { Metadata } from '@protos/blitz_metadata';
import { CatalogItemAccessor } from './catalogItemAccessor';
export class UnpackedMetadataAccessor {
private items: Record<string, CatalogItemAccessor> = {};
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);
}
}
+1
View File
@@ -46,4 +46,5 @@ export * from './gallery';
export * from './metaSortTank'; export * from './metaSortTank';
export * from './popularTanks'; export * from './popularTanks';
export * from './romanize'; export * from './romanize';
export * from './slugify';
export * from './youtubers'; 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;
}
+7 -5
View File
@@ -1,5 +1,5 @@
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { deburr } from 'lodash-es'; import { deburr } from 'lodash-es';
import { SUPPORTED_LOCALES } from '../../../i18n/src/strings';
import { I18nString } from '../protos'; import { I18nString } from '../protos';
import { fetchCamouflageDefinitions } from './camouflageDefinitions'; import { fetchCamouflageDefinitions } from './camouflageDefinitions';
import { fetchTankDefinitions } from './tankDefinitions'; import { fetchTankDefinitions } from './tankDefinitions';
@@ -26,8 +26,8 @@ export async function fetchTankNames() {
searchableNameDeburr, searchableNameDeburr,
camouflages: tank.camouflages camouflages: tank.camouflages
?.map((id) => ?.map((id) =>
SUPPORTED_LOCALES.map( locales.supported.map(
(locale) => ({ locale }) =>
camouflageDefinitions.camouflages[id]?.name.locales[locale], camouflageDefinitions.camouflages[id]?.name.locales[locale],
), ),
) )
@@ -42,9 +42,11 @@ export async function fetchTankNames() {
} }
export const SEARCH_KEYS = [ export const SEARCH_KEYS = [
...SUPPORTED_LOCALES.map((locale) => [ ...locales.supported
.map(({ locale }) => [
`searchableName.locales.${locale}`, `searchableName.locales.${locale}`,
`searchableNameDeburr.locales.${locale}`, `searchableNameDeburr.locales.${locale}`,
]).flat(), ])
.flat(),
'camouflages', 'camouflages',
]; ];
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/average_definitions.proto // source: average_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message CamouflageDefinitions { message CamouflageDefinitions {
map<uint32, Camouflage> camouflages = 1; map<uint32, Camouflage> camouflages = 1;
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/camouflage_definitions.proto // source: camouflage_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message ConsumableDefinitions { message ConsumableDefinitions {
map<uint32, Consumable> consumables = 1; map<uint32, Consumable> consumables = 1;
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/consumable_definitions.proto // source: consumable_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -11,14 +11,14 @@ import { createBaseI18nString, I18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
export enum ConsumableTankCategoryFilterCategory { export enum ConsumableTankCategoryFilterCategory {
CLIP = 0, CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP = 0,
} }
export function consumableTankCategoryFilterCategoryFromJSON(object: any): ConsumableTankCategoryFilterCategory { export function consumableTankCategoryFilterCategoryFromJSON(object: any): ConsumableTankCategoryFilterCategory {
switch (object) { switch (object) {
case 0: case 0:
case "CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP": case "CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP":
return ConsumableTankCategoryFilterCategory.CLIP; return ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP;
default: default:
throw new globalThis.Error( throw new globalThis.Error(
"Unrecognized enum value " + object + " for enum ConsumableTankCategoryFilterCategory", "Unrecognized enum value " + object + " for enum ConsumableTankCategoryFilterCategory",
@@ -28,7 +28,7 @@ export function consumableTankCategoryFilterCategoryFromJSON(object: any): Consu
export function consumableTankCategoryFilterCategoryToJSON(object: ConsumableTankCategoryFilterCategory): string { export function consumableTankCategoryFilterCategoryToJSON(object: ConsumableTankCategoryFilterCategory): string {
switch (object) { switch (object) {
case ConsumableTankCategoryFilterCategory.CLIP: case ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP:
return "CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP"; return "CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP";
default: default:
throw new globalThis.Error( throw new globalThis.Error(
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message EquipmentDefinitions { message EquipmentDefinitions {
map<string, EquipmentPreset> presets = 1; map<string, EquipmentPreset> presets = 1;
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/equipment_definitions.proto // source: equipment_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+1 -1
View File
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message Gallery { message Gallery {
repeated Avatar avatars = 1; repeated Avatar avatars = 1;
+1 -1
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/gallery.proto // source: gallery.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message GameDefinitions { message GameDefinitions {
required string version = 1; required string version = 1;
+1 -1
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/game_definitions.proto // source: game_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+1 -1
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/i18n.proto // source: i18n.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message MapDefinitions { message MapDefinitions {
map<int32, MapDefinition> maps = 1; map<int32, MapDefinition> maps = 1;
+1 -1
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/map_definitions.proto // source: map_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/model_definitions.proto // source: model_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+1 -1
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/popular_tanks.proto // source: popular_tanks.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,8 +2,8 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/consumable_definitions.proto"; import "consumable_definitions.proto";
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message ProvisionDefinitions { message ProvisionDefinitions {
map<uint32, Provision> provisions = 1; map<uint32, Provision> provisions = 1;
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/provision_definitions.proto // source: provision_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/rating_leaderboard.proto // source: rating_leaderboard.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+1 -1
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/reviews.proto // source: reviews.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/skill_definitions.proto // source: skill_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -2,7 +2,7 @@ syntax = "proto2";
package blitzkit; package blitzkit;
import "packages/core/src/protos/i18n.proto"; import "i18n.proto";
message TankDefinitions { message TankDefinitions {
map<uint32, TankDefinition> tanks = 1; map<uint32, TankDefinition> tanks = 1;
+70 -70
View File
@@ -2,7 +2,7 @@
// versions: // versions:
// protoc-gen-ts_proto v2.2.2 // protoc-gen-ts_proto v2.2.2
// protoc v5.28.2 // protoc v5.28.2
// source: packages/core/src/protos/tank_definitions.proto // source: tank_definitions.proto
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -11,18 +11,18 @@ import { createBaseI18nString, I18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
export enum TankPriceType { export enum TankPriceType {
CREDITS = 0, TANK_PRICE_TYPE_CREDITS = 0,
GOLD = 1, TANK_PRICE_TYPE_GOLD = 1,
} }
export function tankPriceTypeFromJSON(object: any): TankPriceType { export function tankPriceTypeFromJSON(object: any): TankPriceType {
switch (object) { switch (object) {
case 0: case 0:
case "TANK_PRICE_TYPE_CREDITS": case "TANK_PRICE_TYPE_CREDITS":
return TankPriceType.CREDITS; return TankPriceType.TANK_PRICE_TYPE_CREDITS;
case 1: case 1:
case "TANK_PRICE_TYPE_GOLD": case "TANK_PRICE_TYPE_GOLD":
return TankPriceType.GOLD; return TankPriceType.TANK_PRICE_TYPE_GOLD;
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankPriceType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankPriceType");
} }
@@ -30,9 +30,9 @@ export function tankPriceTypeFromJSON(object: any): TankPriceType {
export function tankPriceTypeToJSON(object: TankPriceType): string { export function tankPriceTypeToJSON(object: TankPriceType): string {
switch (object) { switch (object) {
case TankPriceType.CREDITS: case TankPriceType.TANK_PRICE_TYPE_CREDITS:
return "TANK_PRICE_TYPE_CREDITS"; return "TANK_PRICE_TYPE_CREDITS";
case TankPriceType.GOLD: case TankPriceType.TANK_PRICE_TYPE_GOLD:
return "TANK_PRICE_TYPE_GOLD"; return "TANK_PRICE_TYPE_GOLD";
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankPriceType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankPriceType");
@@ -40,26 +40,26 @@ export function tankPriceTypeToJSON(object: TankPriceType): string {
} }
export enum ShellType { export enum ShellType {
AP = 0, SHELL_TYPE_AP = 0,
APCR = 1, SHELL_TYPE_APCR = 1,
HEAT = 2, SHELL_TYPE_HEAT = 2,
HE = 3, SHELL_TYPE_HE = 3,
} }
export function shellTypeFromJSON(object: any): ShellType { export function shellTypeFromJSON(object: any): ShellType {
switch (object) { switch (object) {
case 0: case 0:
case "SHELL_TYPE_AP": case "SHELL_TYPE_AP":
return ShellType.AP; return ShellType.SHELL_TYPE_AP;
case 1: case 1:
case "SHELL_TYPE_APCR": case "SHELL_TYPE_APCR":
return ShellType.APCR; return ShellType.SHELL_TYPE_APCR;
case 2: case 2:
case "SHELL_TYPE_HEAT": case "SHELL_TYPE_HEAT":
return ShellType.HEAT; return ShellType.SHELL_TYPE_HEAT;
case 3: case 3:
case "SHELL_TYPE_HE": case "SHELL_TYPE_HE":
return ShellType.HE; return ShellType.SHELL_TYPE_HE;
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum ShellType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum ShellType");
} }
@@ -67,13 +67,13 @@ export function shellTypeFromJSON(object: any): ShellType {
export function shellTypeToJSON(object: ShellType): string { export function shellTypeToJSON(object: ShellType): string {
switch (object) { switch (object) {
case ShellType.AP: case ShellType.SHELL_TYPE_AP:
return "SHELL_TYPE_AP"; return "SHELL_TYPE_AP";
case ShellType.APCR: case ShellType.SHELL_TYPE_APCR:
return "SHELL_TYPE_APCR"; return "SHELL_TYPE_APCR";
case ShellType.HEAT: case ShellType.SHELL_TYPE_HEAT:
return "SHELL_TYPE_HEAT"; return "SHELL_TYPE_HEAT";
case ShellType.HE: case ShellType.SHELL_TYPE_HE:
return "SHELL_TYPE_HE"; return "SHELL_TYPE_HE";
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum ShellType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum ShellType");
@@ -81,30 +81,30 @@ export function shellTypeToJSON(object: ShellType): string {
} }
export enum ModuleType { export enum ModuleType {
VEHICLE = 0, MODULE_TYPE_VEHICLE = 0,
ENGINE = 1, MODULE_TYPE_ENGINE = 1,
TRACKS = 2, MODULE_TYPE_TRACKS = 2,
TURRET = 3, MODULE_TYPE_TURRET = 3,
GUN = 4, MODULE_TYPE_GUN = 4,
} }
export function moduleTypeFromJSON(object: any): ModuleType { export function moduleTypeFromJSON(object: any): ModuleType {
switch (object) { switch (object) {
case 0: case 0:
case "MODULE_TYPE_VEHICLE": case "MODULE_TYPE_VEHICLE":
return ModuleType.VEHICLE; return ModuleType.MODULE_TYPE_VEHICLE;
case 1: case 1:
case "MODULE_TYPE_ENGINE": case "MODULE_TYPE_ENGINE":
return ModuleType.ENGINE; return ModuleType.MODULE_TYPE_ENGINE;
case 2: case 2:
case "MODULE_TYPE_TRACKS": case "MODULE_TYPE_TRACKS":
return ModuleType.TRACKS; return ModuleType.MODULE_TYPE_TRACKS;
case 3: case 3:
case "MODULE_TYPE_TURRET": case "MODULE_TYPE_TURRET":
return ModuleType.TURRET; return ModuleType.MODULE_TYPE_TURRET;
case 4: case 4:
case "MODULE_TYPE_GUN": case "MODULE_TYPE_GUN":
return ModuleType.GUN; return ModuleType.MODULE_TYPE_GUN;
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum ModuleType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum ModuleType");
} }
@@ -112,15 +112,15 @@ export function moduleTypeFromJSON(object: any): ModuleType {
export function moduleTypeToJSON(object: ModuleType): string { export function moduleTypeToJSON(object: ModuleType): string {
switch (object) { switch (object) {
case ModuleType.VEHICLE: case ModuleType.MODULE_TYPE_VEHICLE:
return "MODULE_TYPE_VEHICLE"; return "MODULE_TYPE_VEHICLE";
case ModuleType.ENGINE: case ModuleType.MODULE_TYPE_ENGINE:
return "MODULE_TYPE_ENGINE"; return "MODULE_TYPE_ENGINE";
case ModuleType.TRACKS: case ModuleType.MODULE_TYPE_TRACKS:
return "MODULE_TYPE_TRACKS"; return "MODULE_TYPE_TRACKS";
case ModuleType.TURRET: case ModuleType.MODULE_TYPE_TURRET:
return "MODULE_TYPE_TURRET"; return "MODULE_TYPE_TURRET";
case ModuleType.GUN: case ModuleType.MODULE_TYPE_GUN:
return "MODULE_TYPE_GUN"; return "MODULE_TYPE_GUN";
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum ModuleType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum ModuleType");
@@ -128,26 +128,26 @@ export function moduleTypeToJSON(object: ModuleType): string {
} }
export enum TankClass { export enum TankClass {
LIGHT = 0, TANK_CLASS_LIGHT = 0,
MEDIUM = 1, TANK_CLASS_MEDIUM = 1,
HEAVY = 2, TANK_CLASS_HEAVY = 2,
TANK_DESTROYER = 3, TANK_CLASS_TANK_DESTROYER = 3,
} }
export function tankClassFromJSON(object: any): TankClass { export function tankClassFromJSON(object: any): TankClass {
switch (object) { switch (object) {
case 0: case 0:
case "TANK_CLASS_LIGHT": case "TANK_CLASS_LIGHT":
return TankClass.LIGHT; return TankClass.TANK_CLASS_LIGHT;
case 1: case 1:
case "TANK_CLASS_MEDIUM": case "TANK_CLASS_MEDIUM":
return TankClass.MEDIUM; return TankClass.TANK_CLASS_MEDIUM;
case 2: case 2:
case "TANK_CLASS_HEAVY": case "TANK_CLASS_HEAVY":
return TankClass.HEAVY; return TankClass.TANK_CLASS_HEAVY;
case 3: case 3:
case "TANK_CLASS_TANK_DESTROYER": case "TANK_CLASS_TANK_DESTROYER":
return TankClass.TANK_DESTROYER; return TankClass.TANK_CLASS_TANK_DESTROYER;
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankClass"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankClass");
} }
@@ -155,13 +155,13 @@ export function tankClassFromJSON(object: any): TankClass {
export function tankClassToJSON(object: TankClass): string { export function tankClassToJSON(object: TankClass): string {
switch (object) { switch (object) {
case TankClass.LIGHT: case TankClass.TANK_CLASS_LIGHT:
return "TANK_CLASS_LIGHT"; return "TANK_CLASS_LIGHT";
case TankClass.MEDIUM: case TankClass.TANK_CLASS_MEDIUM:
return "TANK_CLASS_MEDIUM"; return "TANK_CLASS_MEDIUM";
case TankClass.HEAVY: case TankClass.TANK_CLASS_HEAVY:
return "TANK_CLASS_HEAVY"; return "TANK_CLASS_HEAVY";
case TankClass.TANK_DESTROYER: case TankClass.TANK_CLASS_TANK_DESTROYER:
return "TANK_CLASS_TANK_DESTROYER"; return "TANK_CLASS_TANK_DESTROYER";
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankClass"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankClass");
@@ -169,22 +169,22 @@ export function tankClassToJSON(object: TankClass): string {
} }
export enum TankType { export enum TankType {
RESEARCHABLE = 0, TANK_TYPE_RESEARCHABLE = 0,
PREMIUM = 1, TANK_TYPE_PREMIUM = 1,
COLLECTOR = 2, TANK_TYPE_COLLECTOR = 2,
} }
export function tankTypeFromJSON(object: any): TankType { export function tankTypeFromJSON(object: any): TankType {
switch (object) { switch (object) {
case 0: case 0:
case "TANK_TYPE_RESEARCHABLE": case "TANK_TYPE_RESEARCHABLE":
return TankType.RESEARCHABLE; return TankType.TANK_TYPE_RESEARCHABLE;
case 1: case 1:
case "TANK_TYPE_PREMIUM": case "TANK_TYPE_PREMIUM":
return TankType.PREMIUM; return TankType.TANK_TYPE_PREMIUM;
case 2: case 2:
case "TANK_TYPE_COLLECTOR": case "TANK_TYPE_COLLECTOR":
return TankType.COLLECTOR; return TankType.TANK_TYPE_COLLECTOR;
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankType");
} }
@@ -192,11 +192,11 @@ export function tankTypeFromJSON(object: any): TankType {
export function tankTypeToJSON(object: TankType): string { export function tankTypeToJSON(object: TankType): string {
switch (object) { switch (object) {
case TankType.RESEARCHABLE: case TankType.TANK_TYPE_RESEARCHABLE:
return "TANK_TYPE_RESEARCHABLE"; return "TANK_TYPE_RESEARCHABLE";
case TankType.PREMIUM: case TankType.TANK_TYPE_PREMIUM:
return "TANK_TYPE_PREMIUM"; return "TANK_TYPE_PREMIUM";
case TankType.COLLECTOR: case TankType.TANK_TYPE_COLLECTOR:
return "TANK_TYPE_COLLECTOR"; return "TANK_TYPE_COLLECTOR";
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum TankType");
@@ -204,30 +204,30 @@ export function tankTypeToJSON(object: TankType): string {
} }
export enum CrewType { export enum CrewType {
COMMANDER = 0, CREW_TYPE_COMMANDER = 0,
RADIOMAN = 1, CREW_TYPE_RADIOMAN = 1,
GUNNER = 2, CREW_TYPE_GUNNER = 2,
DRIVER = 3, CREW_TYPE_DRIVER = 3,
LOADER = 4, CREW_TYPE_LOADER = 4,
} }
export function crewTypeFromJSON(object: any): CrewType { export function crewTypeFromJSON(object: any): CrewType {
switch (object) { switch (object) {
case 0: case 0:
case "CREW_TYPE_COMMANDER": case "CREW_TYPE_COMMANDER":
return CrewType.COMMANDER; return CrewType.CREW_TYPE_COMMANDER;
case 1: case 1:
case "CREW_TYPE_RADIOMAN": case "CREW_TYPE_RADIOMAN":
return CrewType.RADIOMAN; return CrewType.CREW_TYPE_RADIOMAN;
case 2: case 2:
case "CREW_TYPE_GUNNER": case "CREW_TYPE_GUNNER":
return CrewType.GUNNER; return CrewType.CREW_TYPE_GUNNER;
case 3: case 3:
case "CREW_TYPE_DRIVER": case "CREW_TYPE_DRIVER":
return CrewType.DRIVER; return CrewType.CREW_TYPE_DRIVER;
case 4: case 4:
case "CREW_TYPE_LOADER": case "CREW_TYPE_LOADER":
return CrewType.LOADER; return CrewType.CREW_TYPE_LOADER;
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum CrewType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum CrewType");
} }
@@ -235,15 +235,15 @@ export function crewTypeFromJSON(object: any): CrewType {
export function crewTypeToJSON(object: CrewType): string { export function crewTypeToJSON(object: CrewType): string {
switch (object) { switch (object) {
case CrewType.COMMANDER: case CrewType.CREW_TYPE_COMMANDER:
return "CREW_TYPE_COMMANDER"; return "CREW_TYPE_COMMANDER";
case CrewType.RADIOMAN: case CrewType.CREW_TYPE_RADIOMAN:
return "CREW_TYPE_RADIOMAN"; return "CREW_TYPE_RADIOMAN";
case CrewType.GUNNER: case CrewType.CREW_TYPE_GUNNER:
return "CREW_TYPE_GUNNER"; return "CREW_TYPE_GUNNER";
case CrewType.DRIVER: case CrewType.CREW_TYPE_DRIVER:
return "CREW_TYPE_DRIVER"; return "CREW_TYPE_DRIVER";
case CrewType.LOADER: case CrewType.CREW_TYPE_LOADER:
return "CREW_TYPE_LOADER"; return "CREW_TYPE_LOADER";
default: default:
throw new globalThis.Error("Unrecognized enum value " + object + " for enum CrewType"); throw new globalThis.Error("Unrecognized enum value " + object + " for enum CrewType");
+4 -1
View File
@@ -1,4 +1,7 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"include": ["src/**/*", "../i18n/src/strings.ts"] "include": ["src/**/*", "../i18n/src/strings.ts"],
"compilerOptions": {
"baseUrl": "../../"
}
} }
+2 -87
View File
@@ -1,89 +1,8 @@
import { merge } from 'ts-deepmerge'; import type en from '../strings/en.json';
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';
export type BlitzKitStrings = typeof en; export type BlitzKitStrings = typeof en;
const stringsRaw = { en, es, fr, ja, pl, pt, ru, uk, zh }; export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
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<Record<SupportedLocale, TranslationTree>> = {};
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<SupportedLocale, string> = {
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<SupportedLocale, string> = {
en: '🇺🇸', en: '🇺🇸',
es: '🇪🇸', es: '🇪🇸',
fr: '🇫🇷', fr: '🇫🇷',
@@ -94,7 +13,3 @@ export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
zh: '🇨🇳', zh: '🇨🇳',
pl: '🇵🇱', pl: '🇵🇱',
}; };
export type TranslationTree = { [key: string]: TranslationNode };
export type TranslationNode = string | TranslationTree;
+2 -2
View File
@@ -1,5 +1,5 @@
import { I18nString } from '@blitzkit/core'; import { I18nString } from '@blitzkit/core';
import { DEFAULT_LOCALE } from './strings'; import locales from '../locales.json' with { type: 'json' };
export function unwrapper(locale: string) { export function unwrapper(locale: string) {
return function (i18nString: I18nString) { return function (i18nString: I18nString) {
@@ -7,6 +7,6 @@ export function unwrapper(locale: string) {
return i18nString.locales[locale]; return i18nString.locales[locale];
} }
return i18nString.locales[DEFAULT_LOCALE]; return i18nString.locales[locales.default];
}; };
} }
+67 -15
View File
@@ -6,6 +6,18 @@
}, },
"common": { "common": {
"tiers": {
"0": "Unknown tier",
"1": "I",
"2": "II",
"3": "III",
"4": "IV",
"5": "V",
"6": "VI",
"7": "VII",
"8": "Vintage"
},
"units": { "units": {
"m": "%s1m", "m": "%s1m",
"mm": "%s1mm", "mm": "%s1mm",
@@ -86,15 +98,44 @@
}, },
"nations": { "nations": {
"ussr": "U.S.S.R.", "0": "Unknown nation",
"germany": "Germany", "1": "U.S.S.R.",
"usa": "U.S.A.", "2": "Germany",
"china": "China", "3": "U.S.A.",
"uk": "U.K.", "4": "China",
"france": "France", "5": "France",
"japan": "Japan", "6": "U.K.",
"european": "European Nation", "7": "Japan",
"other": "Hybrid Nation" "8": "Europe",
"9": "Poland",
"10": "Australia",
"11": "Canada",
"12": "Italy",
"13": "Finland",
"14": "Czech",
"15": "Hybrid Nation",
"16": "GDR",
"17": "Ireland",
"18": "Scotland",
"19": "Sweden",
"20": "Argo Coalition",
"21": "Titan Lab",
"22": "Defender",
"23": "Progresston",
"24": "Wasteland",
"25": "Hunters Moon",
"26": "Cold Front",
"27": "The Explorer",
"28": "Fablegear",
"29": "Outlaws",
"30": "GUP",
"31": "Dune",
"32": "Terminator",
"33": "Warhammer",
"34": "Valkirya Chronicles",
"35": "Mecha",
"36": "TMNT",
"37": "Star Trek"
}, },
"nations_adjectives": { "nations_adjectives": {
@@ -109,6 +150,14 @@
"other": "Hybrid" "other": "Hybrid"
}, },
"tank_class": {
"0": "Unknown Class",
"1": "Heavy",
"2": "Light",
"3": "Medium",
"4": "Tank destroyer"
},
"tank_class_short": { "tank_class_short": {
"-1": "unrecognized", "-1": "unrecognized",
"0": "Light", "0": "Light",
@@ -133,10 +182,10 @@
}, },
"tree_type": { "tree_type": {
"-1": "unrecognized", "0": "Unknown tree type",
"0": "Tech tree", "1": "Common",
"1": "Premium", "2": "Premium",
"2": "Collector" "3": "Collector"
}, },
"regions": { "regions": {
@@ -988,6 +1037,8 @@
}, },
"meta": { "meta": {
"compensation": "Compensation",
"free": "Free",
"back": "Back", "back": "Back",
"compare": "Compare", "compare": "Compare",
"swap": { "swap": {
@@ -1001,11 +1052,12 @@
"class": "Class", "class": "Class",
"tier": "Tier", "tier": "Tier",
"type": "Type", "type": "Type",
"dev_id": "DEV: ID", "id": "ID",
"purchase": "Purchase price", "purchase": "Purchase price",
"restoration": "Restoration price", "restoration": "Restoration price",
"sale": "Sale price", "sale": "Sale price",
"research": "Research XP" "set": "Set",
"price": "Price"
}, },
"tech_tree": { "tech_tree": {
+1 -1
View File
@@ -3,7 +3,7 @@ import { mkdir, writeFile } from 'fs/promises';
import { parse as parsePath } from 'path'; import { parse as parsePath } from 'path';
import ProgressBar from 'progress'; import ProgressBar from 'progress';
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
import { dvp } from '../../../submodules/blitzkit-closed/src/dvp'; import { dvp } from '../../../submodules/blitzkit-closed/src/dava/dvp';
import { readStringDVPL } from '../src/core/blitz/readStringDVPL'; import { readStringDVPL } from '../src/core/blitz/readStringDVPL';
import { DATA } from './buildAssets/constants'; import { DATA } from './buildAssets/constants';
import { readYAMLDVPL } from './core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from './core/blitz/readYAMLDVPL';
@@ -22,6 +22,7 @@ import {
ResearchCost, ResearchCost,
ShellType, ShellType,
SkillDefinitions, SkillDefinitions,
slugify,
TankClass, TankClass,
TankDefinitions, TankDefinitions,
TankPrice, TankPrice,
@@ -37,7 +38,6 @@ import {
SUPPORTED_LOCALES, SUPPORTED_LOCALES,
} from '@blitzkit/i18n'; } from '@blitzkit/i18n';
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { deburr } from 'lodash-es';
import { parse as parsePath } from 'path'; import { parse as parsePath } from 'path';
import { Vector3Tuple } from 'three'; import { Vector3Tuple } from 'three';
import { parse as parseYaml } from 'yaml'; import { parse as parseYaml } from 'yaml';
@@ -49,11 +49,6 @@ import { DATA } from './constants';
import { Avatar } from './skillIcons'; import { Avatar } from './skillIcons';
import { TankParameters } from './tankIcons'; import { TankParameters } from './tankIcons';
const nonAlphanumericRegex = /[^a-z0-9]/g;
const multipleDashesRegex = /--+/g;
const trailingDashRegex = /-$/g;
const leadingDashRegex = /^-/g;
const nationSlugDiscriminators = { const nationSlugDiscriminators = {
china: 'cn', china: 'cn',
european: 'eu', european: 'eu',
@@ -740,12 +735,7 @@ export async function definitions() {
? getString(tank.shortUserString) ? getString(tank.shortUserString)
: undefined) ?? getString(tank.userString) : undefined) ?? getString(tank.userString)
).locales.en; ).locales.en;
const slug = slugify(name);
let slug = deburr(name).toLowerCase();
slug = slug.replaceAll(nonAlphanumericRegex, '-');
slug = slug.replaceAll(multipleDashesRegex, '-');
slug = slug.replaceAll(trailingDashRegex, '');
slug = slug.replaceAll(leadingDashRegex, '');
idToNation[tankId] = nation; idToNation[tankId] = nation;
+1 -1
View File
@@ -1,5 +1,5 @@
import { Avatar, encodePBBuffer, fetchGlossary, Gallery } from '@blitzkit/core'; 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 { extname } from 'path';
import ProgressBar from 'progress'; import ProgressBar from 'progress';
import { commitAssets } from '../core/github/commitAssets'; import { commitAssets } from '../core/github/commitAssets';
+24 -16
View File
@@ -1,10 +1,9 @@
import { exec as execSync } from 'child_process'; import { exec as execSync } from 'child_process';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { readdir, rm, writeFile } from 'fs/promises'; import { readdir, rm, writeFile } from 'fs/promises';
import { chunk } from 'lodash-es';
import { promisify } from 'util'; import { promisify } from 'util';
const ROOT = 'packages/core/src/protos';
const exec = promisify(execSync); const exec = promisify(execSync);
let executableFileExtension: string; let executableFileExtension: string;
@@ -15,33 +14,42 @@ if (existsSync('node_modules/.bin/protoc-gen-ts_proto.exe')) {
executableFileExtension = ''; executableFileExtension = '';
} }
const filesRaw = await readdir(`${ROOT}`); await compile('packages/core/src/protos');
await Promise.all( await compile('submodules/blitzkit-closed/src/protos');
filesRaw.map(async (file) => {
if (!file.endsWith('.proto')) {
await rm(`${ROOT}/${file}`);
}
}),
);
const files = filesRaw.filter((file) => file.endsWith('.proto')); async function compile(root: string) {
const filesRaw = await readdir(root);
for (const file of filesRaw) {
if (!file.endsWith('.ts')) continue;
await rm(`${root}/${file}`);
}
const allFiles = filesRaw.filter((file) => file.endsWith('.proto'));
for (const files of chunk(allFiles, 32)) {
const args = [ const args = [
`-I=${root}`,
`--plugin=./node_modules/.bin/protoc-gen-ts_proto${executableFileExtension}`, `--plugin=./node_modules/.bin/protoc-gen-ts_proto${executableFileExtension}`,
'--ts_proto_opt=esModuleInterop=true', '--ts_proto_opt=esModuleInterop=true',
'--ts_proto_opt=oneof=unions-value', '--ts_proto_opt=oneof=unions-value',
'--ts_proto_opt=removeEnumPrefix=true', // '--ts_proto_opt=removeEnumPrefix=true',
'--ts_proto_opt=unrecognizedEnum=false', '--ts_proto_opt=unrecognizedEnum=false',
'--ts_proto_opt=snakeToCamel=false', '--ts_proto_opt=snakeToCamel=false',
'--ts_proto_out=.', `--ts_proto_out=${root}`,
...files.map((file) => `${ROOT}/${file}`), ...files.map((file) => `${root}/${file}`),
]; ];
await exec(`protoc ${args.join(' ')}`); await exec(`protoc ${args.join(' ')}`);
}
await writeFile( await writeFile(
`${ROOT}/index.ts`, `${root}/index.ts`,
'// @ts-nocheck\n' + '// @ts-nocheck\n' +
files allFiles
.sort()
.map((file) => file.replace('.proto', '')) .map((file) => file.replace('.proto', ''))
.map((file) => `export * from './${file}.ts';`) .map((file) => `export * from './${file}.ts';`)
.join('\n'), .join('\n'),
); );
}
+4 -1
View File
@@ -1,4 +1,7 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"include": ["src/**/*"] "include": ["src/**/*"],
"compilerOptions": {
"baseUrl": "../../"
}
} }
+3 -3
View File
@@ -3,13 +3,13 @@
import partytown from '@astrojs/partytown'; import partytown from '@astrojs/partytown';
import react from '@astrojs/react'; import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap'; 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'; import { defineConfig } from 'astro/config';
export default defineConfig({ export default defineConfig({
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: locales.default,
locales: [...SUPPORTED_LOCALES], locales: locales.supported.map(({ locale }) => locale),
routing: { routing: {
prefixDefaultLocale: false, prefixDefaultLocale: false,
}, },
+6 -10
View File
@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@astrojs/partytown": "^2.1.4", "@astrojs/partytown": "^2.1.4",
"@astrolib/seo": "^1.0.0-beta.8", "@astrolib/seo": "^1.0.0-beta.8",
"@blitzkit/closed": "workspace:*",
"@blitzkit/core": "workspace:*", "@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*", "@blitzkit/i18n": "workspace:*",
"@fontsource/inter": "^5.2.5", "@fontsource/inter": "^5.2.5",
@@ -20,10 +21,10 @@
"@nivo/bar": "^0.87.0", "@nivo/bar": "^0.87.0",
"@nivo/line": "^0.88.0", "@nivo/line": "^0.88.0",
"@radix-ui/colors": "^3.0.0", "@radix-ui/colors": "^3.0.0",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/themes": "^3.1.4", "@radix-ui/themes": "^3.2.1",
"@react-three/drei": "^9.115.0", "@react-three/drei": "^10.0.8",
"@react-three/fiber": "^8.17.10", "@react-three/fiber": "^9.1.2",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"immer": "^10.1.1", "immer": "^10.1.1",
@@ -31,9 +32,7 @@
"markdown-to-jsx": "^7.5.0", "markdown-to-jsx": "^7.5.0",
"nanostores": "^0.11.3", "nanostores": "^0.11.3",
"quicklime": "^1.2.0", "quicklime": "^1.2.0",
"react": "^18.3.1",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^18.3.1",
"react-promise-suspense": "^0.3.4", "react-promise-suspense": "^0.3.4",
"three": "^0.169.0", "three": "^0.169.0",
"urlon": "^3.1.0", "urlon": "^3.1.0",
@@ -46,10 +45,7 @@
"@playform/compress": "^0.1.9", "@playform/compress": "^0.1.9",
"@playform/inline": "^0.1.2", "@playform/inline": "^0.1.2",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/three": "^0.160.0", "@types/three": "^0.160.0",
"astro": "^4.16.7", "astro": "^4.16.7"
"astro-compressor": "^1.0.0"
} }
} }
@@ -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 type { ComponentProps, ReactNode } from 'react';
import { ClassHeavy } from './components/ClassHeavy'; import { ClassHeavy } from './components/ClassHeavy';
import { ClassLight } from './components/ClassLight'; import { ClassLight } from './components/ClassLight';
import { ClassMedium } from './components/ClassMedium'; import { ClassMedium } from './components/ClassMedium';
import { ClassTankDestroyer } from './components/ClassTankDestroyer'; import { ClassTankDestroyer } from './components/ClassTankDestroyer';
export const classIcons: Record< export const classIcons_ue: Record<
TankClass, 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 (props: ComponentProps<'svg'>) => ReactNode
> = { > = {
[TankClass.TANK_DESTROYER]: ClassTankDestroyer, [TankClass_dava.TANK_CLASS_TANK_DESTROYER]: ClassTankDestroyer,
[TankClass.HEAVY]: ClassHeavy, [TankClass_dava.TANK_CLASS_HEAVY]: ClassHeavy,
[TankClass.LIGHT]: ClassLight, [TankClass_dava.TANK_CLASS_LIGHT]: ClassLight,
[TankClass.MEDIUM]: ClassMedium, [TankClass_dava.TANK_CLASS_MEDIUM]: ClassMedium,
}; };
@@ -0,0 +1,23 @@
import type { RewardCurrency } from '@protos/blitz_static_reward_currency';
import { Flex, Text, type TextProps } from '@radix-ui/themes';
import { entityName } from 'packages/core/src';
import { useLocale } from '../hooks/useLocale';
type CurrencyProps = TextProps & {
reward: RewardCurrency;
};
export function Currency({ reward, ...props }: CurrencyProps) {
const { locale } = useLocale();
return (
<Text {...props}>
<Flex align="center" gap="1">
{reward.amount.toLocaleString(locale)}
<img
src={`/api/currencies/${entityName(reward.currency_catalog_id)}.webp`}
/>
</Flex>
</Text>
);
}
@@ -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 { literals } from '@blitzkit/i18n/src/literals';
import { Flex, Text } from '@radix-ui/themes'; import { Flex, Text } from '@radix-ui/themes';
import fuzzysort from 'fuzzysort'; import fuzzysort from 'fuzzysort';
@@ -3,11 +3,16 @@ import { Code, Flex, Heading, Text } from '@radix-ui/themes';
import { resolveBranchName } from '../core/blitzkit/branches'; import { resolveBranchName } from '../core/blitzkit/branches';
import { getStrings } from '../core/i18n/getStrings'; import { getStrings } from '../core/i18n/getStrings';
import { literals } from '@blitzkit/i18n/src/literals'; import { literals } from '@blitzkit/i18n/src/literals';
import type { BlitzKitStrings } from 'packages/i18n/src';
const { locale } = Astro.params; interface Props {
locale: string;
}
const { locale } = Astro.props;
const strings = await getStrings(locale);
const branchName = resolveBranchName(locale); const branchName = resolveBranchName(locale);
const isBranchNamed = branchName !== undefined; const isBranchNamed = branchName !== undefined;
const strings = getStrings(locale);
--- ---
<style> <style>
+4 -4
View File
@@ -1,12 +1,12 @@
import { DEFAULT_LOCALE } from '@blitzkit/i18n'; import { DEFAULT_LOCALE } from '@blitzkit/i18n';
import { Link, type LinkProps } from '@radix-ui/themes'; import { Link, type LinkProps } from '@radix-ui/themes';
import type { LocaleAcceptorProps } from '../hooks/useLocale';
type LinkI18nProps = LinkProps & LocaleAcceptorProps; type LinkI18nProps = LinkProps & {
locale: string;
};
export function LinkI18n({ locale, href, ...props }: LinkI18nProps) { export function LinkI18n({ locale, href, ...props }: LinkI18nProps) {
const prefix = const prefix = locale === DEFAULT_LOCALE ? '' : `/${locale}`;
locale === undefined || locale === DEFAULT_LOCALE ? '' : `/${locale}`;
return ( return (
<Link href={href?.startsWith('/') ? `${prefix}${href}` : href} {...props} /> <Link href={href?.startsWith('/') ? `${prefix}${href}` : href} {...props} />
+2 -2
View File
@@ -15,11 +15,11 @@ import { LinkI18n } from './LinkI18n';
export interface Props { export interface Props {
integrated?: boolean; integrated?: boolean;
locale: string;
} }
const { integrated } = Astro.props; const { integrated, locale } = Astro.props;
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool)); const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
const { locale } = Astro.params;
const branchName = resolveBranchName(locale); const branchName = resolveBranchName(locale);
const isBranchNamed = branchName !== undefined; const isBranchNamed = branchName !== undefined;
const strings = getStrings(locale); const strings = getStrings(locale);
@@ -13,7 +13,9 @@ export function Listing({ label, ...props }: ListingProps) {
gap="4" gap="4"
style={{ borderBottom: `1px solid ${Var('gray-a9')}` }} style={{ borderBottom: `1px solid ${Var('gray-a9')}` }}
> >
<Text size="4">{label}</Text> <Text size="4" wrap="nowrap">
{label}
</Text>
<Text align="right" size="4" weight="medium" {...props} /> <Text align="right" size="4" weight="medium" {...props} />
</Flex> </Flex>
); );
@@ -0,0 +1,157 @@
import { Nation, TankType } from '@protos/blitz_static_tank_component';
import { Box, Code, Flex } from '@radix-ui/themes';
import { asset, TIER_ROMAN_NUMERALS } from 'packages/core/src';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { TankopediaEphemeral_ue } from '../../stores/tankopediaEphemeral_ue';
import { classIcons_ue } from '../ClassIcon';
import { Currency } from '../Currency';
import { Listing } from '../Tankopedia/MetaSection/components/Listing';
const NATIONAL_BANNER_POSITION_OVERRIDES: Partial<Record<Nation, string>> = {
[Nation.NATION_GERMANY]: '50% 35%',
[Nation.NATION_FRANCE]: '50% 35%',
[Nation.NATION_OTHER]: '50% 35%',
};
export function MetaSection() {
const tank = TankopediaEphemeral_ue.use((state) => state.tank);
const entity = TankopediaEphemeral_ue.use((state) => state.entity);
const set = TankopediaEphemeral_ue.use((state) => state.set);
const compensation = TankopediaEphemeral_ue.use(
(state) => state.compensation,
);
const catalogueId = TankopediaEphemeral_ue.use((state) => state.entity);
const ClassIcon = classIcons_ue[tank.tank_class];
const { locale, strings, gameStrings } = useLocale();
const reward = set.tank_set_rewards.find(
(reward) =>
reward.tank_set_reward_on_level &&
reward.tank_set_reward_on_level.reward_list.some(
(rewardOnLevel) =>
rewardOnLevel.tank_reward &&
rewardOnLevel.tank_reward.tank_catalog_id === catalogueId,
),
)!;
const price =
reward.tank_set_reward_price?.price_list[0].currency_price!.amount;
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`)}
/>
{strings.common.nations[tank.nation]}
</Flex>
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.class}>
<Flex align="center" gap="1">
<ClassIcon width="1em" height="1em" />
{strings.common.tank_class[tank.tank_class]}
</Flex>
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.tier}>
{TIER_ROMAN_NUMERALS[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
}
>
{strings.common.tree_type[tank.tank_type]}
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.id}>
<Code variant="ghost">
{entity.replace('TankEntity.', '')}
</Code>
</Listing>
</Flex>
<Flex direction="column" width="100%" gap="2">
<Listing label={strings.website.tools.tankopedia.meta.set}>
{gameStrings[set.tank_set_ui!.tank_set_displayed_name]}
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.price}>
<Flex align="center" gap="1">
{reward.tank_set_reward_price === undefined ? (
strings.website.tools.tankopedia.meta.free
) : (
<>
{price?.toLocaleString(locale)}
<img
style={{ width: '1em', height: '1em' }}
alt="xp"
src={
'https://wotblitz-gc.gcdn.co/dlc/ultra_test2_25abc2385a5/assets/Medusa/Currency/T_UI_Currency_Silver_S.webp'
}
/>
</>
)}
</Flex>
</Listing>
{compensation.compensation?.currency_reward && (
<Listing
label={strings.website.tools.tankopedia.meta.compensation}
>
<Currency
reward={compensation.compensation.currency_reward}
/>
</Listing>
)}
</Flex>
</Flex>
</Flex>
</Flex>
</Box>
</Box>
</Flex>
);
}
@@ -6,7 +6,6 @@ import {
type CompositeStats, type CompositeStats,
type CompositeStatsKey, type CompositeStatsKey,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/i18n/strings/en.json';
import { amberDark, blueDark } from '@radix-ui/colors'; import { amberDark, blueDark } from '@radix-ui/colors';
import { Flex, Text, type FlexProps } from '@radix-ui/themes'; import { Flex, Text, type FlexProps } from '@radix-ui/themes';
import { times } from 'lodash-es'; import { times } from 'lodash-es';
@@ -53,7 +52,7 @@ export function BreakdownEmbedCard({
}: BreakdownEmbedCardProps) { }: BreakdownEmbedCardProps) {
const { useEmbedState, useRichText } = const { useEmbedState, useRichText } =
useEmbedStateCurry<typeof breakdownConfig>(); useEmbedStateCurry<typeof breakdownConfig>();
const { unwrap } = useLocale(); const { unwrap, strings } = useLocale();
return ( return (
<Flex <Flex
@@ -0,0 +1,9 @@
import { useEffect } from 'react';
export function TestComponent({ data }: { data: any }) {
useEffect(() => {
console.log(data);
}, []);
return 'TestComponent';
}
@@ -2,20 +2,20 @@
import { assertSecret } from '@blitzkit/core'; import { assertSecret } from '@blitzkit/core';
import { CaretRightIcon } from '@radix-ui/react-icons'; import { CaretRightIcon } from '@radix-ui/react-icons';
import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes'; import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes';
import type { BlitzKitStrings } from 'packages/i18n/src';
import type { Tool } from '../constants/tools'; import type { Tool } from '../constants/tools';
import { getStrings } from '../core/i18n/getStrings';
import { LinkI18n } from './LinkI18n'; import { LinkI18n } from './LinkI18n';
interface Props { interface Props {
tool: Tool; tool: Tool;
strings: BlitzKitStrings;
locale: string;
} }
const { tool } = Astro.props; const { tool, strings, locale } = Astro.props;
const disabled = tool.branches?.every( const disabled = tool.branches?.every(
(branch) => branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH), (branch) => branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
); );
const { locale } = Astro.params;
const strings = getStrings(locale);
const toolStrings = const toolStrings =
strings.website.tools[ strings.website.tools[
(tool.stringsId ?? tool.id) as keyof typeof strings.website.tools (tool.stringsId ?? tool.id) as keyof typeof strings.website.tools
@@ -0,0 +1,3 @@
import { DiscreteMetadataAccessor } from 'packages/core/src';
export const discreteMetadata = new DiscreteMetadataAccessor('/api/catalog');
@@ -0,0 +1,27 @@
import type { TankSetCatalogComponent } from '@protos/blitz_static_tank_set_component';
import type { MetadataAccessor } from 'submodules/blitzkit-closed/src';
export async function tankToSet(metadata: MetadataAccessor, tank: string) {
for (const [id] of metadata.items) {
if (!id.startsWith('TankSetEntity')) continue;
const components = await metadata.get(id);
const set = await components.get<TankSetCatalogComponent>(
'tankSetCatalogComponent',
);
for (const reward of set.tank_set_rewards) {
if (!reward.tank_set_reward_on_level) continue;
for (const rewardOnLevel of reward.tank_set_reward_on_level.reward_list) {
if (!rewardOnLevel.tank_reward) continue;
if (rewardOnLevel.tank_reward.tank_catalog_id === tank) {
return set;
}
}
}
}
throw new Error(`Tank ${tank} not found in any set`);
}
@@ -0,0 +1,9 @@
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 unpackedMetadata = new UnpackedMetadataAccessor(
await requestAutoProxyClient<GetPackedMetadataAsyncResponse>(
'GetPackedMetadataAsyncRequest',
).then(unpackMetadata),
);
+6 -4
View File
@@ -1,6 +1,8 @@
import { DEFAULT_LOCALE, 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 = DEFAULT_LOCALE) { export async function getStrings(locale: string) {
if (locale in STRINGS) return STRINGS[locale as SupportedLocale]; const strings = await import(`../../../../i18n/strings/${locale}.json`);
throw new Error(`Unsupported locale: ${locale}`); return merge({}, en, strings) as BlitzKitStrings;
} }
+18 -27
View File
@@ -1,39 +1,30 @@
import { DEFAULT_LOCALE, unwrapper } from '@blitzkit/i18n'; import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { createContext, useContext, type ReactNode } from 'react'; import type { BlitzKitStrings } from 'packages/i18n/src';
import { getStrings } from '../core/i18n/getStrings'; import { createContext, use, useContext } from 'react';
const LocaleContext = createContext<string | null>(null);
const LocaleContext = createContext<{ interface LocaleProviderProps {
locale: string; locale: string;
localeRaw: string | undefined; children: React.ReactNode;
} | null>(null);
interface LocaleProviderProps extends LocaleAcceptorProps {
children: ReactNode;
} }
export function LocaleProvider({ locale, 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 ( return (
<LocaleContext.Provider <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
value={{ locale: locale ?? DEFAULT_LOCALE, localeRaw: locale }}
>
{children}
</LocaleContext.Provider>
); );
} }
export function useLocale() { export function useLocale() {
const context = useContext(LocaleContext); const locale = useContext(LocaleContext);
const strings = use<BlitzKitStrings>(
fetch(`/api/strings/${locale}.json`).then((response) => response.json()),
);
if (!context) { return { locale, strings };
throw new Error('useLocale must be used within a LocaleProvider');
}
const strings = getStrings(context.locale);
const unwrap = unwrapper(context.locale);
return { locale: context.locale, strings, unwrap };
}
export interface LocaleAcceptorProps {
locale: string | undefined;
} }
+13 -11
View File
@@ -1,5 +1,4 @@
--- ---
import '@fontsource/inter';
import { Flex } from '@radix-ui/themes'; import { Flex } from '@radix-ui/themes';
import { BlitzKitTheme } from '../components/BlitzKitTheme'; import { BlitzKitTheme } from '../components/BlitzKitTheme';
import { Checks } from '../components/Checks'; import { Checks } from '../components/Checks';
@@ -7,16 +6,18 @@ import Footer from '../components/Footer.astro';
import Navbar from '../components/Navbar.astro'; import Navbar from '../components/Navbar.astro';
import Party3 from '../components/Party3.astro'; import Party3 from '../components/Party3.astro';
import './base.css'; import './base.css';
import { DEFAULT_LOCALE } from '@blitzkit/i18n'; import type { BlitzKitStrings } from 'packages/i18n/src';
import { getStrings } from '../core/i18n/getStrings';
export interface Props { export interface Props {
noFooter?: boolean; noFooter?: boolean;
noNavbar?: boolean; noNavbar?: boolean;
integrated?: boolean; integrated?: boolean;
locale: string;
} }
const { noFooter, noNavbar, integrated } = Astro.props; const { noFooter, noNavbar, integrated, locale } = Astro.props;
const locale = Astro.params.locale ?? DEFAULT_LOCALE; const strings = await getStrings(locale);
--- ---
<html lang={locale}> <html lang={locale}>
@@ -30,25 +31,26 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
<Party3 /> <Party3 />
<script> <script>
import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { getBestLocale } from '../core/i18n/getBestLocale'; import { getBestLocale } from '../core/i18n/getBestLocale';
const isAPI = window.location.pathname.startsWith('/api'); const isAPI = window.location.pathname.startsWith('/api');
const isI18nPath = SUPPORTED_LOCALES.some((locale) => const isI18nPath = locales.supported.some(({ locale }) =>
window.location.pathname.startsWith(`/${locale}`), window.location.pathname.startsWith(`/${locale}`),
); );
const bestLocale = getBestLocale(); const bestLocale = getBestLocale();
const currentPathLocale = SUPPORTED_LOCALES.find((locale) => const currentPathLocale = locales.supported.find(({ locale }) =>
window.location.pathname.startsWith(`/${locale}`), window.location.pathname.startsWith(`/${locale}`),
); );
if ( if (
!isAPI && // not an api, duh! !isAPI && // not an api, duh!
localStorage.getItem('preferred-locale') !== null && // no preference, so probably google's crawler localStorage.getItem('preferred-locale') !== null && // no preference, so probably google's crawler
bestLocale !== currentPathLocale // we need to redirect currentPathLocale &&
bestLocale !== currentPathLocale.locale // we need to redirect
) { ) {
window.location.pathname = isI18nPath window.location.pathname = isI18nPath
? window.location.pathname.replace( ? window.location.pathname.replace(
`/${currentPathLocale}`, `/${currentPathLocale.locale}`,
bestLocale === undefined ? '' : `/${bestLocale}`, bestLocale === undefined ? '' : `/${bestLocale}`,
) )
: `/${bestLocale}${window.location.pathname}`; : `/${bestLocale}${window.location.pathname}`;
@@ -61,9 +63,9 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
<body class={noNavbar ? 'no-navbar' : ''}> <body class={noNavbar ? 'no-navbar' : ''}>
<BlitzKitTheme> <BlitzKitTheme>
<Flex direction="column" minHeight="100vh"> <Flex direction="column" minHeight="100vh">
<Navbar {integrated} /> <Navbar {locale} {integrated} />
<Checks {locale} client:only="react" /> <Checks localeContext={{ locale, strings }} client:only="react" />
<slot /> <slot />
-4
View File
@@ -1,9 +1,5 @@
@import '@radix-ui/themes/styles.css'; @import '@radix-ui/themes/styles.css';
.radix-themes {
--default-font-family: 'Inter';
}
body { body {
margin: 0; margin: 0;
} }
@@ -1,13 +1,12 @@
--- ---
import { AstroSeo } from '@astrolib/seo'; import { AstroSeo } from '@astrolib/seo';
import { assertSecret } from '@blitzkit/core'; import { assertSecret } from '@blitzkit/core';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/i18n'; import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { literals } from '@blitzkit/i18n/src/literals'; import { literals } from '@blitzkit/i18n/src/literals';
import { Box, Flex, Heading, Link } from '@radix-ui/themes'; import { Box, Flex, Heading, Link } from '@radix-ui/themes';
import type { GetStaticPaths } from 'astro'; import type { GetStaticPaths } from 'astro';
import { AesonPlug } from '../../components/AesonPlug'; import { AesonPlug } from '../../components/AesonPlug';
import HomeHero from '../../components/HomeHero.astro'; import HomeHero from '../../components/HomeHero.astro';
import { HomePageHotTanks } from '../../components/HomePageHotTanks';
import { Plugs } from '../../components/Plugs'; import { Plugs } from '../../components/Plugs';
import ToolCard from '../../components/ToolCard.astro'; import ToolCard from '../../components/ToolCard.astro';
import { import {
@@ -25,17 +24,16 @@ import { getStrings } from '../../core/i18n/getStrings';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH); const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
const MODE = assertSecret(import.meta.env.MODE); const { locale } = Astro.props;
const locale = Astro.params.locale ?? DEFAULT_LOCALE; const strings = await getStrings(locale);
const strings = getStrings(Astro.params.locale);
export const getStaticPaths = (() => { export const getStaticPaths = (() => {
return [ return locales.supported.map(({ locale, blitz }) => {
...SUPPORTED_LOCALES.filter((locale) => locale !== DEFAULT_LOCALE).map( return {
(locale) => ({ params: { locale } }), params: { locale: locale === locales.default ? undefined : locale },
), props: { locale, blitzLocale: blitz ?? locale },
{ params: { locale: undefined } }, };
]; });
}) satisfies GetStaticPaths; }) satisfies GetStaticPaths;
--- ---
@@ -52,8 +50,8 @@ export const getStaticPaths = (() => {
twitter={{ cardType: 'summary_large_image' }} twitter={{ cardType: 'summary_large_image' }}
/> />
<BaseLayout> <BaseLayout {locale}>
<HomeHero /> <HomeHero {locale} />
<!-- <Flex justify="center" py="6" px="4" justify="center"> <!-- <Flex justify="center" py="6" px="4" justify="center">
<Flex <Flex
@@ -72,7 +70,7 @@ export const getStaticPaths = (() => {
</Flex> --> </Flex> -->
{ {
locale !== DEFAULT_LOCALE && ( locale !== locales.default && (
<Flex justify="center" mb="4"> <Flex justify="center" mb="4">
<Link <Link
href="https://crowdin.com/project/blitzkrieg" href="https://crowdin.com/project/blitzkrieg"
@@ -81,7 +79,9 @@ export const getStaticPaths = (() => {
> >
{literals(strings.website.home.help_translate, [ {literals(strings.website.home.help_translate, [
'BlitzKit', 'BlitzKit',
strings.common.locales[locale], strings.common.locales[
locale as keyof typeof strings.common.locales
],
])} ])}
</Link> </Link>
</Flex> </Flex>
@@ -100,29 +100,29 @@ export const getStaticPaths = (() => {
<Flex p="4" justify="center" gap="4" mt="3"> <Flex p="4" justify="center" gap="4" mt="3">
<Flex maxWidth="80rem" flexGrow="1" gap="4" direction="column"> <Flex maxWidth="80rem" flexGrow="1" gap="4" direction="column">
<ToolCard tool={tankopediaTool} /> <ToolCard {locale} {strings} tool={tankopediaTool} />
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
{/* <ToolCard tool={playerStatsTool} /> */} {/* <ToolCard {locale} {strings} tool={playerStatsTool} /> */}
<ToolCard tool={compareTool} /> <ToolCard {locale} {strings} tool={compareTool} />
<ToolCard tool={performanceTool} /> <ToolCard {locale} {strings} tool={performanceTool} />
</Flex> </Flex>
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
<!-- <ToolCard tool={chartsTool} /> --> <!-- <ToolCard {locale} {strings} tool={chartsTool} /> -->
<!-- <ToolCard tool={guessTool} /> --> <!-- <ToolCard {locale} {strings} tool={guessTool} /> -->
<ToolCard tool={galleryTool} /> <ToolCard {locale} {strings} tool={galleryTool} />
<ToolCard tool={sessionTool} /> <ToolCard {locale} {strings} tool={sessionTool} />
</Flex> </Flex>
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
<ToolCard tool={discordTool} /> <ToolCard {locale} {strings} tool={discordTool} />
<ToolCard tool={tierListTool} /> <ToolCard {locale} {strings} tool={tierListTool} />
</Flex> </Flex>
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
<ToolCard tool={embedTool} /> <ToolCard {locale} {strings} tool={embedTool} />
<ToolCard tool={moreTool} /> <ToolCard {locale} {strings} tool={moreTool} />
</Flex> </Flex>
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
@@ -135,6 +135,8 @@ export const getStaticPaths = (() => {
<Flex gap="4" mb="6" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" mb="6" direction={{ initial: 'column', sm: 'row' }}>
<ToolCard <ToolCard
{locale}
{strings}
tool={{ tool={{
button: { color: 'blue' }, button: { color: 'blue' },
id: 'blitz-analysis', id: 'blitz-analysis',
@@ -143,6 +145,8 @@ export const getStaticPaths = (() => {
}} }}
/> />
<ToolCard <ToolCard
{locale}
{strings}
tool={{ tool={{
button: { color: 'purple', highContrast: true }, button: { color: 'purple', highContrast: true },
id: 'blitz-insider', id: 'blitz-insider',
@@ -0,0 +1,26 @@
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 {
tankContext: TankopediaEphemeral_ue;
}
export function Page({ localeContext, tankContext }: PageProps) {
return (
<LocaleProvider data={localeContext}>
<App.Provider>
<TankopediaEphemeral_ue.Provider data={tankContext}>
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
<MetaSection />
</PageWrapper>
</TankopediaEphemeral_ue.Provider>
</App.Provider>
</LocaleProvider>
);
}
@@ -0,0 +1,67 @@
---
import type {
CatalogItemsComponent,
CompensationComponent,
TankCatalogComponent,
} from '@blitzkit/closed/src/unreal/protos';
import { slugify } from '@blitzkit/core';
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import type { GetStaticPaths } from 'astro';
import { tankToSet } from 'packages/website/src/core/blitz/tankToSet';
import type { TankopediaEphemeral_ue } from 'packages/website/src/stores/tankopediaEphemeral_ue';
import { unpackedMetadata } from '../../../../core/blitz/unpackedMetadata';
import BaseLayout from '../../../../layouts/BaseLayout.astro';
import { getStaticPaths as parentGetStaticPaths } from '../index.astro';
import { Page } from './_index';
export const getStaticPaths = (async () => {
const tanks = await unpackedMetadata.getComponent<CatalogItemsComponent>(
'CatalogItemsGroupEntity.all_tanks',
'catalogItemsGroupComponent',
);
const gameStringsDefault = await unpackedMetadata.stringsSubset(
locales.default,
['TankEntity'],
);
const parentPaths = parentGetStaticPaths();
return (
await Promise.all(
tanks.catalog_items.map(async (item) => {
const tank = await unpackedMetadata.get(item).then((components) => {
return components.get<TankCatalogComponent>('tankCatalogComponent');
});
const nameEn = gameStringsDefault[tank.name!.value];
const slug = slugify(nameEn) || item.replace('TankEntity.', '');
return parentPaths.map((parent) => {
return {
params: { ...parent.params, slug },
props: { ...parent.props, entity: item },
};
});
}),
)
).flat();
}) satisfies GetStaticPaths;
const { entity, locale, strings, blitzLocale } = Astro.props;
const set = await tankToSet(unpackedMetadata, entity);
const gameStrings = await unpackedMetadata.stringsSubset(blitzLocale, [
set.tank_set_ui!.tank_set_displayed_name,
]);
const tankEntity = await unpackedMetadata.get(entity);
const tank = await tankEntity.get<TankCatalogComponent>('tankCatalogComponent');
const compensation = await tankEntity.get<CompensationComponent>(
'compensationComponent',
);
const tankContext: TankopediaEphemeral_ue = { tank, set, compensation, entity };
---
<BaseLayout {locale} {strings}>
<Page
client:load
{tankContext}
localeContext={{ locale, strings, gameStrings }}
/>
</BaseLayout>
@@ -0,0 +1,21 @@
---
import { CatalogItemsComponent } from '@protos/blitz_static_catalog_items_group_component';
import { Link } from '@radix-ui/themes';
import { PageWrapper } from 'packages/website/src/components/PageWrapper';
import { unpackedMetadata } from 'packages/website/src/core/blitz/unpackedMetadata';
import BaseLayout from 'packages/website/src/layouts/BaseLayout.astro';
export { getStaticPaths } from '../index.astro';
const { locale } = Astro.props;
const allTanks = unpackedMetadata
.get('CatalogItemsGroupEntity.all_tanks')
.get(CatalogItemsComponent, 'catalogItemsGroupComponent');
---
<BaseLayout {locale}>
<PageWrapper>
{allTanks.catalog_items.map((tank) => <Link>{tank}</Link>)}
</PageWrapper>
</BaseLayout>
@@ -0,0 +1,17 @@
---
import { CatalogItemsComponent } from '@protos/blitz_static_catalog_items_group_component';
import { TestComponent } from 'packages/website/src/components/TestComponent';
import { unpackedMetadata } from 'packages/website/src/core/blitz/unpackedMetadata';
import { getStrings } from 'packages/website/src/core/i18n/getStrings';
export { getStaticPaths } from '../index.astro';
const { locale } = Astro.props;
const strings = await getStrings(locale);
const allTanks = unpackedMetadata
.get('CatalogItemsGroupEntity.all_tanks')
.get(CatalogItemsComponent, 'catalogItemsGroupComponent');
---
<TestComponent client:load data={allTanks} />
@@ -1,43 +0,0 @@
import { TankSandbox } from '../../../../../components/Tankopedia/HeroSection/components/TankSandbox';
import { awaitableModelDefinitions } from '../../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions';
import { awaitableTankDefinitions } from '../../../../../core/awaitables/tankDefinitions';
import { LocaleProvider } from '../../../../../hooks/useLocale';
import { App } from '../../../../../stores/app';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
const [provisionDefinitions, tankDefinitions, modelDefinitions] =
await Promise.all([
awaitableProvisionDefinitions,
awaitableTankDefinitions,
awaitableModelDefinitions,
]);
interface PageProps {
id: number;
}
export function Page({ id }: PageProps) {
const tank = tankDefinitions.tanks[id];
const model = modelDefinitions.models[id];
return (
<LocaleProvider locale="en">
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider data={{ provisionDefinitions, model, tank }}>
<TankopediaEphemeral.Provider data={model}>
<Content />
</TankopediaEphemeral.Provider>
</Duel.Provider>
</TankopediaPersistent.Provider>
</App.Provider>
</LocaleProvider>
);
}
function Content() {
return <TankSandbox naked thicknessRange={{ value: 0 }} />;
}
@@ -1,30 +0,0 @@
---
import { assertSecret } from '@blitzkit/core';
import type { GetStaticPaths } from 'astro';
import { getStaticPaths as tankopediaGetStaticPaths } from '../../../../[...locale]/tools/tankopedia/[id]/index.astro';
import { Page } from './_index';
export const getStaticPaths = (async () => {
if (assertSecret(import.meta.env.MODE) === 'development') {
return await tankopediaGetStaticPaths();
} else {
return [];
}
}) satisfies GetStaticPaths;
const { id } = Astro.params;
---
<html>
<head>
<style is:global>
body {
margin: 0;
}
</style>
</head>
<body>
<Page {id} client:load />
</body>
</html>
@@ -1,4 +1,4 @@
import en from '@blitzkit/i18n/strings/en.json'; import en from '@blitzkit/i18n/strings/en.json' with { type: 'json' };
import type { TankPerformanceSortType } from '.'; import type { TankPerformanceSortType } from '.';
export const TankPerformanceSortTypeNamesArray = Object.keys( export const TankPerformanceSortTypeNamesArray = Object.keys(
@@ -1,9 +1,9 @@
import type en from '@blitzkit/i18n/strings/en.json'; import type { BlitzKitStrings } from 'packages/i18n/src';
import { create } from 'zustand'; import { create } from 'zustand';
import { createContextualStore } from '../../core/zustand/createContextualStore'; import { createContextualStore } from '../../core/zustand/createContextualStore';
export type TankPerformanceSortType = export type TankPerformanceSortType =
keyof typeof en.website.tools.performance.table.stats; keyof BlitzKitStrings['website']['tools']['performance']['table']['stats'];
export interface TankPerformanceSort { export interface TankPerformanceSort {
type: TankPerformanceSortType; type: TankPerformanceSortType;
@@ -0,0 +1,21 @@
import type { CompensationComponent } from '@protos/blitz_static_compensation_component';
import type { TankCatalogComponent } from '@protos/blitz_static_tank_component';
import type { TankSetCatalogComponent } from '@protos/blitz_static_tank_set_component';
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import { createContextualStore } from '../core/zustand/createContextualStore';
export interface TankopediaEphemeral_ue {
tank: TankCatalogComponent;
compensation: CompensationComponent;
set: TankSetCatalogComponent;
entity: string;
}
export const TankopediaEphemeral_ue = createContextualStore(
(data: TankopediaEphemeral_ue) => {
return create<TankopediaEphemeral_ue>()(
subscribeWithSelector<TankopediaEphemeral_ue>(() => data),
);
},
);
@@ -1,10 +1,11 @@
import type en from '@blitzkit/i18n/strings/en.json';
import * as lodash from 'lodash-es'; import * as lodash from 'lodash-es';
import type { BlitzKitStrings } from 'packages/i18n/src';
import { create } from 'zustand'; import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware'; import { persist, subscribeWithSelector } from 'zustand/middleware';
import { createContextualStore } from '../../core/zustand/createContextualStore'; import { createContextualStore } from '../../core/zustand/createContextualStore';
export type TankopediaSortBy = keyof typeof en.website.common.tank_search.sort; export type TankopediaSortBy =
keyof BlitzKitStrings['website']['common']['tank_search']['sort'];
export type TankopediaSortDirection = 'ascending' | 'descending'; export type TankopediaSortDirection = 'ascending' | 'descending';
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only'; export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
+2 -1
View File
@@ -3,6 +3,7 @@
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react", "jsxImportSource": "react",
"types": ["vite/client"] "types": ["vite/client"],
"baseUrl": "../../"
} }
} }
+8 -1
View File
@@ -14,11 +14,18 @@
"allowJs": true, "allowJs": true,
"incremental": true, "incremental": true,
"isolatedModules": true, "isolatedModules": true,
"plugins": [ "plugins": [
{ {
"name": "next" "name": "next"
} }
] ],
"baseUrl": "./",
"paths": {
"@protos/*": ["submodules/blitzkit-closed/src/unreal/protos/*"],
"@strings/*": ["packages/i18n/strings/*"]
}
}, },
"exclude": ["node_modules/**/*", "dist", ".next"] "exclude": ["node_modules/**/*", "dist", ".next"]
} }