Merge branch 'main' into dev

This commit is contained in:
tresabhi committed 2025-01-19 12:41:06 -06:00
commit d6a1357a82
28 files changed
+7530 -3685

No files matched your search

+15 -1
View File
@@ -39,6 +39,17 @@ env:
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }} PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }} PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
PUBLIC_ASSET_BRANCH: ${{ github.event.inputs.cdn_branch || secrets.PUBLIC_ASSET_BRANCH }} PUBLIC_ASSET_BRANCH: ${{ github.event.inputs.cdn_branch || secrets.PUBLIC_ASSET_BRANCH }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: true
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
PUBLIC_DEBUG_MISSING_I18N: false
jobs: jobs:
download_client: download_client:
@@ -106,6 +117,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: Setup bun - name: Setup bun
uses: oven-sh/setup-bun@v1 uses: oven-sh/setup-bun@v1
@@ -127,7 +141,7 @@ jobs:
- name: Start server and build assets - name: Start server and build assets
run: | run: |
bun dev:website & bun dev:website --verbosepa &
SERVER_PID=$! SERVER_PID=$!
bun run build:assets --all-targets --depot --production bun run build:assets --all-targets --depot --production
+2
View File
@@ -26,6 +26,8 @@ env:
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }} PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }} PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
jobs: jobs:
build: build:
+1 -1
View File
@@ -1,3 +1,3 @@
files: files:
- source: /packages/core/lang/en.json - source: /packages/i18n/strings/en.json
translation: /%original_path%/%locale%.json translation: /%original_path%/%locale%.json
+7
View File
@@ -0,0 +1,7 @@
# BlitzKit 1.27.1
## Fixes
- Fixed a crash that occurs if tanks are deleted in Blitz and were once available in BlitzKit
- All tank icons are now back
- The tank search once again returns results
+3 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "blitzkit", "name": "blitzkit",
"private": true, "private": true,
"version": "1.27.0", "version": "1.27.1",
"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",
@@ -35,7 +35,8 @@
"build:protos": "bun packages/scripts/src/protos.ts", "build:protos": "bun packages/scripts/src/protos.ts",
"preview:website": "cd packages/website && bun run preview", "preview:website": "cd packages/website && bun run preview",
"build:reviews": "cd packages/scripts && bun build:reviews", "build:reviews": "cd packages/scripts && bun build:reviews",
"build:ids": "cd packages/scripts && bun build:ids" "build:ids": "cd packages/scripts && bun build:ids",
"prunestrings": "cd packages/scripts && bun prunestrings"
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+2 -4
View File
@@ -1,3 +1,4 @@
import { SEARCH_KEYS } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n'; import { literals } from '@blitzkit/i18n';
import { Locale } from 'discord.js'; import { Locale } from 'discord.js';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
@@ -19,10 +20,7 @@ export async function resolveTankId(
const searchResult = go( const searchResult = go(
`${tank}`, `${tank}`,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{ { keys: SEARCH_KEYS, limit: 1 },
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
limit: 1,
},
); );
if (searchResult.length === 0) { if (searchResult.length === 0) {
@@ -1,5 +1,4 @@
import { fetchTankNames, TankType } from '@blitzkit/core'; import { fetchTankNames, SEARCH_KEYS, TankType } from '@blitzkit/core';
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { import {
ApplicationCommandOptionChoiceData, ApplicationCommandOptionChoiceData,
AutocompleteInteraction, AutocompleteInteraction,
@@ -32,16 +31,7 @@ export async function autocompleteTanks(
go( go(
focusedOption.value, focusedOption.value,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{ { keys: SEARCH_KEYS, limit: 10 },
keys: [
...SUPPORTED_LOCALES.map((locale) => [
`searchableName.{locale}`,
`searchableNameDeburr.{locale}`,
]).flat(),
'camouflages',
],
limit: 10,
},
).map(async (item) => { ).map(async (item) => {
let name = unwrap(item.obj.searchableName); let name = unwrap(item.obj.searchableName);
@@ -3,7 +3,8 @@ import { Locale } from 'discord.js';
import { SUPPORTED_LOCALES_DISCORD } from '../localization/strings/constants'; import { SUPPORTED_LOCALES_DISCORD } from '../localization/strings/constants';
import { translator } from '../localization/translator'; import { translator } from '../localization/translator';
const validNameRegex = /^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u; const validNameRegex =
/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u;
export function localizationObject( export function localizationObject(
path: (strings: BlitzKitStrings) => string, path: (strings: BlitzKitStrings) => string,
+9
View File
@@ -1,4 +1,5 @@
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';
@@ -33,3 +34,11 @@ export async function fetchTankNames() {
}), }),
); );
} }
export const SEARCH_KEYS = [
...SUPPORTED_LOCALES.map((locale) => [
`searchableName.locales.${locale}`,
`searchableNameDeburr.locales.${locale}`,
]).flat(),
'camouflages',
];
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -10,7 +10,8 @@
"build:reviews": "bun src/reviews.ts", "build:reviews": "bun src/reviews.ts",
"build:ids": "bun src/discoverIds.ts", "build:ids": "bun src/discoverIds.ts",
"build:bot": "bun src/buildBot.ts", "build:bot": "bun src/buildBot.ts",
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts" "build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
"prunestrings": "bun src/pruneStrings.ts"
}, },
"dependencies": { "dependencies": {
"@blitzkit/core": "workspace:*", "@blitzkit/core": "workspace:*",
+16 -10
View File
@@ -1,4 +1,5 @@
import { assertSecret } from '@blitzkit/core'; import { assertSecret } from '@blitzkit/core';
import { existsSync } from 'fs';
import { mkdir, writeFile } from 'fs/promises'; 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';
@@ -45,19 +46,24 @@ while (true) {
files.length, files.length,
); );
await Promise.all( for (const { path, data } of files) {
files.map(async ({ path, data }) => { console.log(`Patching "${path}"...`);
const { dir } = parsePath(path);
const { dir } = parsePath(path);
try {
await mkdir(`${DATA}/${dir}`, { recursive: true }); await mkdir(`${DATA}/${dir}`, { recursive: true });
await writeFile( } catch (error) {
`${DATA}/${path}`, console.warn(`Failed to make directory "${dir}"`);
new Uint8Array(writeDVPL(Buffer.from(data))), }
);
bar.tick(); await writeFile(
}), `${DATA}/${path}`,
); new Uint8Array(writeDVPL(Buffer.from(data))),
);
bar.tick();
}
if ('dynamicContentLocalizationsDir' in data) { if ('dynamicContentLocalizationsDir' in data) {
console.log('Found dynamic content localizations; patching...'); console.log('Found dynamic content localizations; patching...');
@@ -3,7 +3,7 @@ import { DATA_COMPRESSED } from '../../buildAssets/constants';
import { readDVPL } from './readDVPL'; import { readDVPL } from './readDVPL';
export async function readDVPLFile(file: string) { export async function readDVPLFile(file: string) {
if (DATA_COMPRESSED) { if (DATA_COMPRESSED && !file.endsWith('.dvpl')) {
return readDVPL(await readFile(`${file}.dvpl`)); return readDVPL(await readFile(`${file}.dvpl`));
} }
+49
View File
@@ -0,0 +1,49 @@
import {
DEFAULT_LOCALE,
SUPPORTED_LOCALES,
TranslationTree,
} from '@blitzkit/i18n';
import { writeFile } from 'fs/promises';
const ROOT = '@blitzkit/i18n/strings';
const WORKING_ROOT = '../../packages/i18n/strings';
const defaultStrings = await import(`${ROOT}/${DEFAULT_LOCALE}.json`);
const nonDefaultLocales = SUPPORTED_LOCALES.filter(
(locale) => locale !== DEFAULT_LOCALE,
);
function prune(defaultStrings: TranslationTree, strings: TranslationTree) {
for (const key in strings) {
if (key in defaultStrings) {
if (typeof defaultStrings[key] === typeof strings[key]) {
if (typeof strings[key] === 'string') {
if (
(defaultStrings[key] as string).replaceAll(/%s\d/g, '%s') ===
(strings[key] as string).replaceAll(/%s\d/g, '%s') ||
strings[key] === ''
) {
delete strings[key];
}
} else {
prune(defaultStrings[key] as TranslationTree, strings[key]);
}
} else {
delete strings[key];
}
} else {
delete strings[key];
}
}
return strings;
}
for (const locale of nonDefaultLocales) {
console.log(`Pruning ${locale}...`);
const strings = await import(`${ROOT}/${locale}.json`);
const pruned = prune(defaultStrings, strings);
writeFile(`${WORKING_ROOT}/${locale}.json`, JSON.stringify(pruned, null, 2));
}
@@ -1,4 +1,9 @@
import { asset, TankType, type TankDefinition } from '@blitzkit/core'; import {
asset,
fetchTankDefinitions,
TankType,
type TankDefinition,
} from '@blitzkit/core';
import { Flex, Text, type TextProps } from '@radix-ui/themes'; import { Flex, Text, type TextProps } from '@radix-ui/themes';
import { uniq } from 'lodash-es'; import { uniq } from 'lodash-es';
import { forwardRef, type ReactNode } from 'react'; import { forwardRef, type ReactNode } from 'react';
@@ -16,6 +21,8 @@ type TankCardProps = TextProps & {
noLink?: boolean; noLink?: boolean;
}; };
const tankDefinitions = await fetchTankDefinitions();
export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>( export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
( (
{ {
@@ -49,10 +56,9 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
onClick={() => { onClick={() => {
onSelect?.(tank); onSelect?.(tank);
mutateTankopediaPersistent((draft) => { mutateTankopediaPersistent((draft) => {
draft.recentlyViewed = uniq([ draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
tank.id, .filter((id) => id in tankDefinitions.tanks)
...draft.recentlyViewed, .slice(0, MAX_RECENTLY_VIEWED);
]).slice(0, MAX_RECENTLY_VIEWED);
}); });
}} }}
className="tank-search-card" className="tank-search-card"
@@ -12,7 +12,7 @@ export function NoResults({ type = 'filters' }: NoResultsProps) {
return ( return (
<Flex flexGrow="1" align="center" justify="center"> <Flex flexGrow="1" align="center" justify="center">
<Text color="gray"> <Text color="gray">
{strings.website.common.tank_search.no_tanks_found.body}.{' '} {strings.website.common.tank_search.no_tanks_found.body}{' '}
<Link <Link
href="#" href="#"
underline="always" underline="always"
@@ -25,7 +25,6 @@ export function NoResults({ type = 'filters' }: NoResultsProps) {
? strings.website.common.tank_search.no_tanks_found.clear_filters ? strings.website.common.tank_search.no_tanks_found.clear_filters
: strings.website.common.tank_search.no_tanks_found.search_again} : strings.website.common.tank_search.no_tanks_found.search_again}
</Link> </Link>
.
</Text> </Text>
</Flex> </Flex>
); );
@@ -16,7 +16,9 @@ export function RecentlyViewed() {
const tankopediaPersistentStore = TankopediaPersistent.useStore(); const tankopediaPersistentStore = TankopediaPersistent.useStore();
const filters = useStore($tankFilters); const filters = useStore($tankFilters);
// non-reactive because it is a little weird that it updates instantly even before the page loads // non-reactive because it is a little weird that it updates instantly even before the page loads
const recentlyViewed = tankopediaPersistentStore.getState().recentlyViewed; const recentlyViewed = tankopediaPersistentStore
.getState()
.recentlyViewed.filter((id) => id in tankDefinitions.tanks);
const sort = useStore($tankopediaSort); const sort = useStore($tankopediaSort);
const hasFilters = useMemo( const hasFilters = useMemo(
() => () =>
@@ -2,6 +2,7 @@ import {
metaSortTank, metaSortTank,
normalizeBoundingBox, normalizeBoundingBox,
resolveDpm, resolveDpm,
SEARCH_KEYS,
unionBoundingBox, unionBoundingBox,
type TankDefinition, type TankDefinition,
} from '@blitzkit/core'; } from '@blitzkit/core';
@@ -410,7 +411,7 @@ export const TankSearch = memo<TankSearchProps>(
: sorted.reverse(); : sorted.reverse();
} else { } else {
const searchedRaw = fuzzysort.go(tankFilters.search, tankNames, { const searchedRaw = fuzzysort.go(tankFilters.search, tankNames, {
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'], keys: SEARCH_KEYS,
}); });
const searchedTanks = searchedRaw.map( const searchedTanks = searchedRaw.map(
(result) => tankDefinitions.tanks[result.obj.id], (result) => tankDefinitions.tanks[result.obj.id],
@@ -456,7 +457,9 @@ export const TankSearch = memo<TankSearchProps>(
draft.recentlyViewed = uniq([ draft.recentlyViewed = uniq([
...tanksFiltered.map(({ id }) => id), ...tanksFiltered.map(({ id }) => id),
...draft.recentlyViewed, ...draft.recentlyViewed,
]).slice(0, MAX_RECENTLY_VIEWED); ])
.filter((id) => id in tankDefinitions.tanks)
.slice(0, MAX_RECENTLY_VIEWED);
}); });
}} }}
> >
@@ -1,4 +1,4 @@
import { assertSecret } from '@blitzkit/core'; import { assertSecret, fetchTankDefinitions } from '@blitzkit/core';
import { MinusCircledIcon } from '@radix-ui/react-icons'; import { MinusCircledIcon } from '@radix-ui/react-icons';
import { Callout, Flex } from '@radix-ui/themes'; import { Callout, Flex } from '@radix-ui/themes';
import { uniq } from 'lodash-es'; import { uniq } from 'lodash-es';
@@ -9,6 +9,8 @@ import { AesonPlug } from '../AesonPlug';
import { ExperimentIcon } from '../ExperimentIcon'; import { ExperimentIcon } from '../ExperimentIcon';
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants'; import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
const tankDefinitions = await fetchTankDefinitions();
export function CalloutsSection() { export function CalloutsSection() {
const tank = Duel.use((state) => state.protagonist.tank); const tank = Duel.use((state) => state.protagonist.tank);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
@@ -17,10 +19,9 @@ export function CalloutsSection() {
useEffect(() => { useEffect(() => {
mutateTankopediaPersistent((draft) => { mutateTankopediaPersistent((draft) => {
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed]).slice( draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
0, .filter((id) => id in tankDefinitions.tanks)
MAX_RECENTLY_VIEWED, .slice(0, MAX_RECENTLY_VIEWED);
);
}); });
}, [tank]); }, [tank]);
@@ -30,6 +30,7 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { getBestLocale } from '../core/i18n/getBestLocale'; import { getBestLocale } from '../core/i18n/getBestLocale';
const isAPI = window.location.pathname.startsWith('/api');
const isI18nPath = SUPPORTED_LOCALES.some((locale) => const isI18nPath = SUPPORTED_LOCALES.some((locale) =>
window.location.pathname.startsWith(`/${locale}`), window.location.pathname.startsWith(`/${locale}`),
); );
@@ -38,7 +39,7 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
window.location.pathname.startsWith(`/${locale}`), window.location.pathname.startsWith(`/${locale}`),
); );
if (bestLocale !== currentPathLocale) { if (!isAPI && bestLocale !== currentPathLocale) {
window.location.pathname = isI18nPath window.location.pathname = isI18nPath
? window.location.pathname.replace( ? window.location.pathname.replace(
`/${currentPathLocale}`, `/${currentPathLocale}`,
@@ -2,6 +2,7 @@ import { TankSandbox } from '../../../../../components/Tankopedia/HeroSection/co
import { awaitableModelDefinitions } from '../../../../../core/awaitables/modelDefinitions'; import { awaitableModelDefinitions } from '../../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions'; import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions';
import { awaitableTankDefinitions } from '../../../../../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../../../../../core/awaitables/tankDefinitions';
import { LocaleProvider } from '../../../../../hooks/useLocale';
import { App } from '../../../../../stores/app'; import { App } from '../../../../../stores/app';
import { Duel } from '../../../../../stores/duel'; import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
@@ -23,15 +24,17 @@ export function Page({ id }: PageProps) {
const model = modelDefinitions.models[id]; const model = modelDefinitions.models[id];
return ( return (
<App.Provider> <LocaleProvider locale="en">
<TankopediaPersistent.Provider> <App.Provider>
<Duel.Provider data={{ provisionDefinitions, tank }}> <TankopediaPersistent.Provider>
<TankopediaEphemeral.Provider data={model}> <Duel.Provider data={{ provisionDefinitions, tank }}>
<Content /> <TankopediaEphemeral.Provider data={model}>
</TankopediaEphemeral.Provider> <Content />
</Duel.Provider> </TankopediaEphemeral.Provider>
</TankopediaPersistent.Provider> </Duel.Provider>
</App.Provider> </TankopediaPersistent.Provider>
</App.Provider>
</LocaleProvider>
); );
} }