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_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
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:
download_client:
@@ -106,6 +117,9 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: Setup bun
uses: oven-sh/setup-bun@v1
@@ -127,7 +141,7 @@ jobs:
- name: Start server and build assets
run: |
bun dev:website &
bun dev:website --verbosepa &
SERVER_PID=$!
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_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
jobs:
build:
+1 -1
View File
@@ -1,3 +1,3 @@
files:
- source: /packages/core/lang/en.json
- source: /packages/i18n/strings/en.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",
"private": true,
"version": "1.27.0",
"version": "1.27.1",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
@@ -35,7 +35,8 @@
"build:protos": "bun packages/scripts/src/protos.ts",
"preview:website": "cd packages/website && bun run preview",
"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",
"dependencies": {
+2 -4
View File
@@ -1,3 +1,4 @@
import { SEARCH_KEYS } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n';
import { Locale } from 'discord.js';
import { go } from 'fuzzysort';
@@ -19,10 +20,7 @@ export async function resolveTankId(
const searchResult = go(
`${tank}`,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
limit: 1,
},
{ keys: SEARCH_KEYS, limit: 1 },
);
if (searchResult.length === 0) {
@@ -1,5 +1,4 @@
import { fetchTankNames, TankType } from '@blitzkit/core';
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { fetchTankNames, SEARCH_KEYS, TankType } from '@blitzkit/core';
import {
ApplicationCommandOptionChoiceData,
AutocompleteInteraction,
@@ -32,16 +31,7 @@ export async function autocompleteTanks(
go(
focusedOption.value,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{
keys: [
...SUPPORTED_LOCALES.map((locale) => [
`searchableName.{locale}`,
`searchableNameDeburr.{locale}`,
]).flat(),
'camouflages',
],
limit: 10,
},
{ keys: SEARCH_KEYS, limit: 10 },
).map(async (item) => {
let name = unwrap(item.obj.searchableName);
@@ -3,7 +3,8 @@ import { Locale } from 'discord.js';
import { SUPPORTED_LOCALES_DISCORD } from '../localization/strings/constants';
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(
path: (strings: BlitzKitStrings) => string,
+9
View File
@@ -1,4 +1,5 @@
import { deburr } from 'lodash-es';
import { SUPPORTED_LOCALES } from '../../../i18n/src/strings';
import { I18nString } from '../protos';
import { fetchCamouflageDefinitions } from './camouflageDefinitions';
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:ids": "bun src/discoverIds.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": {
"@blitzkit/core": "workspace:*",
+16 -10
View File
@@ -1,4 +1,5 @@
import { assertSecret } from '@blitzkit/core';
import { existsSync } from 'fs';
import { mkdir, writeFile } from 'fs/promises';
import { parse as parsePath } from 'path';
import ProgressBar from 'progress';
@@ -45,19 +46,24 @@ while (true) {
files.length,
);
await Promise.all(
files.map(async ({ path, data }) => {
const { dir } = parsePath(path);
for (const { path, data } of files) {
console.log(`Patching "${path}"...`);
const { dir } = parsePath(path);
try {
await mkdir(`${DATA}/${dir}`, { recursive: true });
await writeFile(
`${DATA}/${path}`,
new Uint8Array(writeDVPL(Buffer.from(data))),
);
} catch (error) {
console.warn(`Failed to make directory "${dir}"`);
}
bar.tick();
}),
);
await writeFile(
`${DATA}/${path}`,
new Uint8Array(writeDVPL(Buffer.from(data))),
);
bar.tick();
}
if ('dynamicContentLocalizationsDir' in data) {
console.log('Found dynamic content localizations; patching...');
@@ -3,7 +3,7 @@ import { DATA_COMPRESSED } from '../../buildAssets/constants';
import { readDVPL } from './readDVPL';
export async function readDVPLFile(file: string) {
if (DATA_COMPRESSED) {
if (DATA_COMPRESSED && !file.endsWith('.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 { uniq } from 'lodash-es';
import { forwardRef, type ReactNode } from 'react';
@@ -16,6 +21,8 @@ type TankCardProps = TextProps & {
noLink?: boolean;
};
const tankDefinitions = await fetchTankDefinitions();
export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
(
{
@@ -49,10 +56,9 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
onClick={() => {
onSelect?.(tank);
mutateTankopediaPersistent((draft) => {
draft.recentlyViewed = uniq([
tank.id,
...draft.recentlyViewed,
]).slice(0, MAX_RECENTLY_VIEWED);
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
.filter((id) => id in tankDefinitions.tanks)
.slice(0, MAX_RECENTLY_VIEWED);
});
}}
className="tank-search-card"
@@ -12,7 +12,7 @@ export function NoResults({ type = 'filters' }: NoResultsProps) {
return (
<Flex flexGrow="1" align="center" justify="center">
<Text color="gray">
{strings.website.common.tank_search.no_tanks_found.body}.{' '}
{strings.website.common.tank_search.no_tanks_found.body}{' '}
<Link
href="#"
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.search_again}
</Link>
.
</Text>
</Flex>
);
@@ -16,7 +16,9 @@ export function RecentlyViewed() {
const tankopediaPersistentStore = TankopediaPersistent.useStore();
const filters = useStore($tankFilters);
// 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 hasFilters = useMemo(
() =>
@@ -2,6 +2,7 @@ import {
metaSortTank,
normalizeBoundingBox,
resolveDpm,
SEARCH_KEYS,
unionBoundingBox,
type TankDefinition,
} from '@blitzkit/core';
@@ -410,7 +411,7 @@ export const TankSearch = memo<TankSearchProps>(
: sorted.reverse();
} else {
const searchedRaw = fuzzysort.go(tankFilters.search, tankNames, {
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
keys: SEARCH_KEYS,
});
const searchedTanks = searchedRaw.map(
(result) => tankDefinitions.tanks[result.obj.id],
@@ -456,7 +457,9 @@ export const TankSearch = memo<TankSearchProps>(
draft.recentlyViewed = uniq([
...tanksFiltered.map(({ id }) => id),
...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 { Callout, Flex } from '@radix-ui/themes';
import { uniq } from 'lodash-es';
@@ -9,6 +9,8 @@ import { AesonPlug } from '../AesonPlug';
import { ExperimentIcon } from '../ExperimentIcon';
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
const tankDefinitions = await fetchTankDefinitions();
export function CalloutsSection() {
const tank = Duel.use((state) => state.protagonist.tank);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
@@ -17,10 +19,9 @@ export function CalloutsSection() {
useEffect(() => {
mutateTankopediaPersistent((draft) => {
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed]).slice(
0,
MAX_RECENTLY_VIEWED,
);
draft.recentlyViewed = uniq([tank.id, ...draft.recentlyViewed])
.filter((id) => id in tankDefinitions.tanks)
.slice(0, MAX_RECENTLY_VIEWED);
});
}, [tank]);
@@ -30,6 +30,7 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { getBestLocale } from '../core/i18n/getBestLocale';
const isAPI = window.location.pathname.startsWith('/api');
const isI18nPath = SUPPORTED_LOCALES.some((locale) =>
window.location.pathname.startsWith(`/${locale}`),
);
@@ -38,7 +39,7 @@ const locale = Astro.params.locale ?? DEFAULT_LOCALE;
window.location.pathname.startsWith(`/${locale}`),
);
if (bestLocale !== currentPathLocale) {
if (!isAPI && bestLocale !== currentPathLocale) {
window.location.pathname = isI18nPath
? window.location.pathname.replace(
`/${currentPathLocale}`,
@@ -2,6 +2,7 @@ import { TankSandbox } from '../../../../../components/Tankopedia/HeroSection/co
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';
@@ -23,15 +24,17 @@ export function Page({ id }: PageProps) {
const model = modelDefinitions.models[id];
return (
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider data={{ provisionDefinitions, tank }}>
<TankopediaEphemeral.Provider data={model}>
<Content />
</TankopediaEphemeral.Provider>
</Duel.Provider>
</TankopediaPersistent.Provider>
</App.Provider>
<LocaleProvider locale="en">
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider data={{ provisionDefinitions, tank }}>
<TankopediaEphemeral.Provider data={model}>
<Content />
</TankopediaEphemeral.Provider>
</Duel.Provider>
</TankopediaPersistent.Provider>
</App.Provider>
</LocaleProvider>
);
}