feat: localize game assets

This commit is contained in:
tresabhi committed 2025-01-14 19:22:41 -06:00
1 parent d8459d373c
commit ef9778ea96
90 files changed
+336 -251

No files matched your search

+1
View File
@@ -189,6 +189,7 @@
"unpickled", "unpickled",
"Unregistering", "Unregistering",
"unrotate", "unrotate",
"unwrapper",
"vercel", "vercel",
"vickers", "vickers",
"vitepress", "vitepress",
BIN
View File
Binary file not shown.
+1
View File
@@ -5,6 +5,7 @@
"types": "src/types.d.ts", "types": "src/types.d.ts",
"dependencies": { "dependencies": {
"@blitzkit/core": "workspace:*", "@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*",
"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",
@@ -32,7 +32,7 @@ export async function renderBreakdown(
filters: StatFilters, filters: StatFilters,
locale: Locale, locale: Locale,
) { ) {
const { t, translate } = translator(locale); const { t, translate, unwrap } = translator(locale);
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await blitzStarsTankAverages; const awaitedTankAverages = await blitzStarsTankAverages;
const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale); const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale);
@@ -190,8 +190,9 @@ export async function renderBreakdown(
class={tankDefinition?.class} class={tankDefinition?.class}
type={tankDefinition?.type} type={tankDefinition?.type}
title={ title={
tankDefinition?.name ?? tankDefinition
translate('bot.commands.breakdown.body.unknown_tank', [`${id}`]) ? unwrap(tankDefinition.name)
: translate('bot.commands.breakdown.body.unknown_tank', [`${id}`])
} }
stats={[ stats={[
{ {
+2 -2
View File
@@ -27,7 +27,7 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
.addStringOption(addUsernameChoices), .addStringOption(addUsernameChoices),
async handler(interaction) { async handler(interaction) {
const { t, translate } = translator(interaction.locale); const { t, translate, unwrap } = translator(interaction.locale);
const tier = Number(interaction.options.getString('tier')); const tier = Number(interaction.options.getString('tier'));
const { id, region } = await resolvePlayerFromCommand(interaction); const { id, region } = await resolvePlayerFromCommand(interaction);
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
@@ -77,7 +77,7 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
) )
.map( .map(
(tank) => (tank) =>
`[${flags[tank.nation]} ${markdownEscape(tank.name)}](<https://blitzkit.app/tools/tankopedia/${tank.id}>)`, `[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tools/tankopedia/${tank.id}>)`,
); );
const lines = [title, ...tankList]; const lines = [title, ...tankList];
+13 -3
View File
@@ -6,6 +6,7 @@ import {
tankIcon, tankIcon,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { Locale } from 'discord.js';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import { CommandWrapper } from '../components/CommandWrapper'; import { CommandWrapper } from '../components/CommandWrapper';
import { TitleBar } from '../components/TitleBar'; import { TitleBar } from '../components/TitleBar';
@@ -18,6 +19,7 @@ import { RenderConfiguration } from '../core/blitzkit/renderConfiguration';
import { blitzStarsTankAverages } from '../core/blitzstars/tankAverages'; import { blitzStarsTankAverages } from '../core/blitzstars/tankAverages';
import { buttonLink } from '../core/discord/buttonLink'; import { buttonLink } from '../core/discord/buttonLink';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from '../core/localization/translator';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config'; import { theme } from '../stitches.config';
@@ -218,11 +220,13 @@ function stat(value: string | number, flex = 2) {
} }
async function playerListing( async function playerListing(
locale: Locale,
player: WotInspectorReplayPlayerData, player: WotInspectorReplayPlayerData,
protagonistTeam: boolean, protagonistTeam: boolean,
winningTeam: number, winningTeam: number,
recordingPlayer = false, recordingPlayer = false,
) { ) {
const { unwrap } = translator(locale);
const awaitedTankAverages = await blitzStarsTankAverages; const awaitedTankAverages = await blitzStarsTankAverages;
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const tank = awaitedTankDefinitions.tanks[player.vehicle_descr]; const tank = awaitedTankDefinitions.tanks[player.vehicle_descr];
@@ -275,7 +279,7 @@ async function playerListing(
}} }}
> >
<img <img
alt={tank.name} alt={unwrap(tank.name)}
src={await iconPng(tankIcon(tank.id))} src={await iconPng(tankIcon(tank.id))}
style={{ style={{
width: 32, width: 32,
@@ -312,7 +316,7 @@ async function playerListing(
fontSize: 16, fontSize: 16,
}} }}
> >
{tank.name} {unwrap(tank.name)}
</span> </span>
</div> </div>
@@ -530,6 +534,7 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
) )
.map((player) => .map((player) =>
playerListing( playerListing(
interaction.locale,
player, player,
true, true,
replayData.winner_team, replayData.winner_team,
@@ -553,7 +558,12 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
players players
.filter((player) => player.team === antagonistTeamId) .filter((player) => player.team === antagonistTeamId)
.map((player) => .map((player) =>
playerListing(player, false, replayData.winner_team), playerListing(
interaction.locale,
player,
false,
replayData.winner_team,
),
), ),
)} )}
</div> </div>
+12 -12
View File
@@ -77,7 +77,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
.addStringOption(addUsernameChoices), .addStringOption(addUsernameChoices),
async handler(interaction) { async handler(interaction) {
const { t, translate } = translator(interaction.locale); const { t, translate, unwrap } = translator(interaction.locale);
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const targetTankId = await resolveTankId( const targetTankId = await resolveTankId(
interaction.options.getString('target-tank', true), interaction.options.getString('target-tank', true),
@@ -99,7 +99,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
if (targetTankId === startingTankId) { if (targetTankId === startingTankId) {
return translate('bot.commands.research.errors.start_end_equal', [ return translate('bot.commands.research.errors.start_end_equal', [
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
]); ]);
} }
@@ -107,28 +107,28 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
if (startingTank.tier > targetTank.tier) { if (startingTank.tier > targetTank.tier) {
return translate('bot.commands.research.errors.unordered', [ return translate('bot.commands.research.errors.unordered', [
escapeMarkdown(startingTank.name), escapeMarkdown(unwrap(startingTank.name)),
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
]); ]);
} }
if (!targetTankAncestry.includes(startingTankId)) { if (!targetTankAncestry.includes(startingTankId)) {
return translate('bot.commands.research.errors.tanks_not_on_line', [ return translate('bot.commands.research.errors.tanks_not_on_line', [
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
escapeMarkdown(startingTank.name), escapeMarkdown(unwrap(startingTank.name)),
]); ]);
} }
} else { } else {
if (targetTank.type !== TankType.RESEARCHABLE) { if (targetTank.type !== TankType.RESEARCHABLE) {
return translate('bot.commands.research.errors.non_tech_tree', [ return translate('bot.commands.research.errors.non_tech_tree', [
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
]); ]);
} }
if (targetTank.tier === 1) { if (targetTank.tier === 1) {
return translate('bot.commands.research.errors.tier_1', [ return translate('bot.commands.research.errors.tier_1', [
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
]); ]);
} }
@@ -140,7 +140,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
return translate('bot.commands.research.errors.already_researched', [ return translate('bot.commands.research.errors.already_researched', [
escapeMarkdown(targetTank.name), escapeMarkdown(unwrap(targetTank.name)),
]); ]);
} }
@@ -452,7 +452,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
}} }}
> >
<img <img
alt={tank.name} alt={unwrap(tank.name)}
src={TANK_ICONS[tank.class]} src={TANK_ICONS[tank.class]}
width={16} width={16}
height={16} height={16}
@@ -463,7 +463,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
color: theme.colors.textHighContrast, color: theme.colors.textHighContrast,
}} }}
> >
{tank.name} {unwrap(tank.name)}
</span> </span>
</div> </div>
</div> </div>
@@ -1,15 +1,17 @@
import { fetchTankNames, TankType } from '@blitzkit/core'; import { fetchTankNames, TankType } from '@blitzkit/core';
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { import {
ApplicationCommandOptionChoiceData, ApplicationCommandOptionChoiceData,
AutocompleteInteraction, AutocompleteInteraction,
CacheType, CacheType,
} from 'discord.js'; } from 'discord.js';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { tankNames } from '../blitzkit/nonBlockingPromises';
import { translator } from '../localization/translator';
import { import {
DISCORD_CHOICES_MAX_NAME_SIZE, DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX, OVERFLOW_SUFFIX,
} from './autocompleteClan/constants'; } from './autocompleteClan/constants';
import { tankNames } from '../blitzkit/nonBlockingPromises';
export const tankNamesTechTreeOnly = fetchTankNames().then((names) => export const tankNamesTechTreeOnly = fetchTankNames().then((names) =>
names.filter((tank) => tank.treeType === TankType.RESEARCHABLE), names.filter((tank) => tank.treeType === TankType.RESEARCHABLE),
@@ -20,6 +22,7 @@ export async function autocompleteTanks(
techTreeOnly = false, techTreeOnly = false,
fields = ['tank'], fields = ['tank'],
) { ) {
const { unwrap } = translator(interaction.locale);
const focusedOption = interaction.options.getFocused(true); const focusedOption = interaction.options.getFocused(true);
if (!fields.includes(focusedOption.name)) return; if (!fields.includes(focusedOption.name)) return;
@@ -30,18 +33,24 @@ export async function autocompleteTanks(
focusedOption.value, focusedOption.value,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{ {
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'], keys: [
...SUPPORTED_LOCALES.map((locale) => [
`searchableName.${locale}`,
`searchableNameDeburr.${locale}`,
]).flat(),
'camouflages',
],
limit: 10, limit: 10,
}, },
).map(async (item) => { ).map(async (item) => {
let name = item.obj.searchableName; let name = unwrap(item.obj.searchableName);
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
name = `${item.obj.searchableName.slice( name = `${unwrap(item.obj.searchableName).slice(
0, 0,
item.obj.searchableName.length - unwrap(item.obj.searchableName).length -
overSize - overSize -
OVERFLOW_SUFFIX.length, OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX}`; )}${OVERFLOW_SUFFIX}`;
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE, SupportedLocale } from '@blitzkit/core'; import { DEFAULT_LOCALE, SupportedLocale } from '@blitzkit/i18n';
import { Locale } from 'discord.js'; import { Locale } from 'discord.js';
const SUPPORTED_LOCALES_DISCORD_MAP: Record<SupportedLocale, Locale> = { const SUPPORTED_LOCALES_DISCORD_MAP: Record<SupportedLocale, Locale> = {
@@ -1,8 +1,10 @@
import { unwrapper } from '@blitzkit/i18n';
import { Locale } from 'discord.js'; import { Locale } from 'discord.js';
import { translations } from './strings'; import { translations } from './strings';
import { import {
DEFAULT_LOCALE_DISCORD, DEFAULT_LOCALE_DISCORD,
SUPPORTED_LOCALES_DISCORD, SUPPORTED_LOCALES_DISCORD,
SUPPORTED_LOCALES_DISCORD_MAP_INVERSE,
} from './strings/constants'; } from './strings/constants';
export function translator(localeRaw: Locale) { export function translator(localeRaw: Locale) {
@@ -74,5 +76,7 @@ export function translator(localeRaw: Locale) {
.join(''); .join('');
} }
return { locale, translate, t }; const unwrap = unwrapper(SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]);
return { locale, translate, t, unwrap };
} }
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts"] "include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"]
} }
-1
View File
@@ -46,5 +46,4 @@ export * from './votes';
export * from './createDefaultProvisions'; export * from './createDefaultProvisions';
export * from './metaSortTank'; export * from './metaSortTank';
export * from './romanize'; export * from './romanize';
export * from './supportedLocales';
export * from './youtubers'; export * from './youtubers';
+8 -1
View File
@@ -1,4 +1,5 @@
import { deburr } from 'lodash-es'; import { deburr } from 'lodash-es';
import { I18nString } from '../protos';
import { fetchCamouflageDefinitions } from './camouflageDefinitions'; import { fetchCamouflageDefinitions } from './camouflageDefinitions';
import { fetchTankDefinitions } from './tankDefinitions'; import { fetchTankDefinitions } from './tankDefinitions';
@@ -11,12 +12,18 @@ export async function fetchTankNames() {
return await Promise.all( return await Promise.all(
tankDefinitionsArray.map(async (tank) => { tankDefinitionsArray.map(async (tank) => {
const searchableNameDeburr: I18nString = { locales: {} };
Object.entries(tank.name_full ?? tank.name).forEach(([key, value]) => {
searchableNameDeburr.locales[key] = deburr(value);
});
return { return {
id: tank.id, id: tank.id,
name: tank.name, name: tank.name,
nameFull: tank.name_full, nameFull: tank.name_full,
searchableName: tank.name_full ?? tank.name, searchableName: tank.name_full ?? tank.name,
searchableNameDeburr: deburr(tank.name_full ?? tank.name), searchableNameDeburr,
camouflages: tank.camouflages camouflages: tank.camouflages
?.map((id) => camouflageDefinitions.camouflages[id]?.name) ?.map((id) => camouflageDefinitions.camouflages[id]?.name)
.filter(Boolean) .filter(Boolean)
+1
View File
@@ -1,6 +1,7 @@
export * from './blitz'; export * from './blitz';
export * from './blitzkit'; export * from './blitzkit';
export * from './databases'; export * from './databases';
export * from './imgur';
export * from './math'; export * from './math';
export * from './patreon'; export * from './patreon';
export * from './protobuf'; export * from './protobuf';
@@ -6,7 +6,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
@@ -6,7 +6,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
@@ -6,7 +6,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
+1 -1
View File
@@ -6,7 +6,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
+1 -1
View File
@@ -18,7 +18,7 @@ export interface I18nString_LocalesEntry {
value: string; value: string;
} }
function createBaseI18nString(): I18nString { export function createBaseI18nString(): I18nString {
return { locales: {} }; return { locales: {} };
} }
+1 -1
View File
@@ -6,7 +6,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
@@ -7,7 +7,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { ConsumableTankFilter } from "./consumable_definitions"; import { ConsumableTankFilter } from "./consumable_definitions";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
+1 -1
View File
@@ -6,7 +6,7 @@
/* eslint-disable */ /* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { I18nString } from "./i18n"; import { I18nString, createBaseI18nString } from "./i18n";
export const protobufPackage = "blitzkit"; export const protobufPackage = "blitzkit";
+2 -6
View File
@@ -3,10 +3,6 @@
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"dependencies": { "dependencies": {},
}, "devDependencies": {}
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/lz4js": "^0.2.1"
}
} }
+2
View File
@@ -1 +1,3 @@
export * from './strings'; export * from './strings';
export * from './supportedLocales';
export * from './unwrapper';
+4 -4
View File
@@ -1,12 +1,12 @@
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/core/src/blitzkit';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import type en from '../strings/en.json'; import type en from '../strings/en.json';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from './supportedLocales';
export type TranslationTree = { [key: string]: TranslationNode }; export type TranslationTree = { [key: string]: TranslationNode };
export type TranslationNode = string | TranslationTree; export type TranslationNode = string | TranslationTree;
const files = import.meta.glob('../../lang/*.json', { eager: true }); const files = import.meta.glob('../strings/*.json', { eager: true });
export const localizedStrings: Record<string, typeof en> = {}; export const localizedStrings: Record<string, typeof en> = {};
function nuke(object: TranslationTree) { function nuke(object: TranslationTree) {
@@ -21,8 +21,8 @@ function nuke(object: TranslationTree) {
} }
SUPPORTED_LOCALES.forEach((locale) => { SUPPORTED_LOCALES.forEach((locale) => {
const defaultStrings = files[`../../lang/${DEFAULT_LOCALE}.json`]; const defaultStrings = files[`../strings/${DEFAULT_LOCALE}.json`];
const strings = files[`../../lang/${locale}.json`]; const strings = files[`../strings/${locale}.json`];
const mergedStrings = merge({}, defaultStrings, strings); const mergedStrings = merge({}, defaultStrings, strings);
/** /**
+12
View File
@@ -0,0 +1,12 @@
import { I18nString } from '@blitzkit/core';
import { DEFAULT_LOCALE } from './supportedLocales';
export function unwrapper(locale: string) {
return function (i18nString: I18nString) {
if (locale in i18nString.locales) {
return i18nString.locales[locale];
}
return i18nString.locales[DEFAULT_LOCALE];
};
}
-9
View File
@@ -1,9 +0,0 @@
{
"name": "prate",
"private": true,
"type": "module",
"main": "src/index.ts",
"devDependencies": {
"@blitzkit/core": "workspace:*"
}
}
+4 -4
View File
@@ -9,7 +9,7 @@ import { DATA } from './buildAssets/constants';
import { readYAMLDVPL } from './core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from './core/blitz/readYAMLDVPL';
import { writeDVPL } from './core/blitz/writeDVPL'; import { writeDVPL } from './core/blitz/writeDVPL';
const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`); const versionTextFile = await readStringDVPL(`${DATA}/version.txt`);
const currentVersion = versionTextFile const currentVersion = versionTextFile
.split(' ')[0] .split(' ')[0]
.split('.') .split('.')
@@ -51,7 +51,7 @@ while (true) {
await mkdir(`${DATA}/${dir}`, { recursive: true }); await mkdir(`${DATA}/${dir}`, { recursive: true });
await writeFile( await writeFile(
`${DATA}/${path}.dvpl`, `${DATA}/${path}`,
new Uint8Array(writeDVPL(Buffer.from(data))), new Uint8Array(writeDVPL(Buffer.from(data))),
); );
@@ -69,13 +69,13 @@ while (true) {
); );
const newStrings = parseYaml(await localizationsResponse.text()); const newStrings = parseYaml(await localizationsResponse.text());
const oldStrings = await readYAMLDVPL<Record<string, string>>( const oldStrings = await readYAMLDVPL<Record<string, string>>(
`${DATA}/Strings/en.yaml.dvpl`, `${DATA}/Strings/en.yaml`,
); );
const patchedStrings = { ...oldStrings, ...newStrings }; const patchedStrings = { ...oldStrings, ...newStrings };
const patchedContent = stringifyYaml(patchedStrings); const patchedContent = stringifyYaml(patchedStrings);
await writeFile( await writeFile(
`${DATA}/Strings/en.yaml.dvpl`, `${DATA}/Strings/en.yaml`,
new Uint8Array(writeDVPL(Buffer.from(patchedContent))), new Uint8Array(writeDVPL(Buffer.from(patchedContent))),
); );
} }
@@ -8,16 +8,16 @@ import { DATA } from './constants';
export async function boosterIcons() { export async function boosterIcons() {
const boosterFiles = (await readdir(`${DATA}/Gfx/Shared/boosters`)).filter( const boosterFiles = (await readdir(`${DATA}/Gfx/Shared/boosters`)).filter(
(file) => !file.endsWith('@2x.txt.dvpl') && !file.startsWith('texture0'), (file) => !file.includes('@2x.txt') && !file.startsWith('texture0'),
); );
const image = sharp( const image = sharp(
await readDVPLFile(`${DATA}/Gfx/Shared/boosters/texture0.packed.webp.dvpl`), await readDVPLFile(`${DATA}/Gfx/Shared/boosters/texture0.packed.webp`),
); );
const changes: FileChange[] = []; const changes: FileChange[] = [];
await Promise.all( await Promise.all(
boosterFiles.map(async (file) => { boosterFiles.map(async (file) => {
const name = file.match(/booster_(.+).txt.dvpl/)?.[1]; const name = file.match(/booster_(.+).txt/)?.[1];
if (name === undefined) return; if (name === undefined) return;
@@ -6,7 +6,7 @@ import { DATA } from './constants';
export async function camouflageIcons() { export async function camouflageIcons() {
console.log('Building camouflage icons...'); console.log('Building camouflage icons...');
const content = await readDVPLFile( const content = await readDVPLFile(
`${DATA}/Gfx/UI/Hangar/IconCamouflage.packed.webp.dvpl`, `${DATA}/Gfx/UI/Hangar/IconCamouflage.packed.webp`,
); );
const changes: FileChange[] = [ const changes: FileChange[] = [
{ {
+28 -6
View File
@@ -1,11 +1,33 @@
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { argv } from 'process'; import { argv } from 'process';
const WINDOWS_APPS = 'C:/Program Files/WindowsApps';
const wotbPackageRegex = /7458BE2C\..+_\d+(\.\d+)+_x64__.+/;
const isDepot = argv.includes('--depot'); const isDepot = argv.includes('--depot');
export const DATA = isDepot let data: string;
? await (async () => { let compressed: boolean;
const [installationVersion] = await readdir(`../../depots/444202`);
return `../../depots/444202/${installationVersion}/Data`; if (isDepot) {
})() const [installationVersion] = await readdir(`../../depots/444202`);
: 'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data'; data = `../../depots/444202/${installationVersion}/Data`;
compressed = true;
} else {
const directories = await readdir(WINDOWS_APPS).then((files) =>
files.filter((file) => wotbPackageRegex.test(file)),
);
if (directories.length !== 1) {
throw new Error(
`Expected 1 directory for WoTB Windows App, got ${directories.length}.`,
);
}
const [windowsApp] = directories;
data = `${WINDOWS_APPS}/${windowsApp}/Data`;
compressed = false;
}
export const DATA = data;
export const DATA_COMPRESSED = compressed;
@@ -34,21 +34,19 @@ export async function consumableProvisionIcons() {
const styleSheets = await Promise.all( const styleSheets = await Promise.all(
styles.map(async (path) => styles.map(async (path) =>
Object.values( Object.values(
(await readYAMLDVPL<Mappings>(`${DATA}/${path}.dvpl`)).StyleSheets, (await readYAMLDVPL<Mappings>(`${DATA}/${path}`)).StyleSheets,
), ),
), ),
).then((array) => array.flat()); ).then((array) => array.flat());
for (const match of ( for (const match of (
await readStringDVPL( await readStringDVPL(
`${DATA}/XML/item_defs/vehicles/common/consumables/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/consumables/list.xml`,
) )
).matchAll(listItemsPattern)) { ).matchAll(listItemsPattern)) {
const consumablesCommon = await readXMLDVPL<{ const consumablesCommon = await readXMLDVPL<{
root: ConsumablesCommon; root: ConsumablesCommon;
}>( }>(`${DATA}/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`);
`${DATA}/XML/item_defs/vehicles/common/consumables/${match[1]}.xml.dvpl`,
);
await Promise.all( await Promise.all(
Object.values(consumablesCommon.root).map(async (consumable) => { Object.values(consumablesCommon.root).map(async (consumable) => {
@@ -70,9 +68,9 @@ export async function consumableProvisionIcons() {
.replace('.psd', '') .replace('.psd', '')
.replace('.txt', ''); .replace('.txt', '');
if (existsSync(`${DATA}/${configPath}.packed.webp.dvpl`)) { if (existsSync(`${DATA}/${configPath}.packed.webp`)) {
const image = sharp( const image = sharp(
await readDVPLFile(`${DATA}/${configPath}.packed.webp.dvpl`), await readDVPLFile(`${DATA}/${configPath}.packed.webp`),
); );
const content = await image.trim({ threshold: 100 }).toBuffer(); const content = await image.trim({ threshold: 100 }).toBuffer();
@@ -83,10 +81,10 @@ export async function consumableProvisionIcons() {
} else { } else {
const consumablesTexture = sharp( const consumablesTexture = sharp(
await readDVPLFile( await readDVPLFile(
`${`${DATA}/${configPath}`.split('/').slice(0, -1).join('/')}/texture0.packed.webp.dvpl`, `${`${DATA}/${configPath}`.split('/').slice(0, -1).join('/')}/texture0.packed.webp`,
), ),
); );
const sizes = (await readStringDVPL(`${DATA}/${configPath}.txt.dvpl`)) const sizes = (await readStringDVPL(`${DATA}/${configPath}.txt`))
.split('\n')[4] .split('\n')[4]
.split(' ') .split(' ')
.map(Number); .map(Number);
@@ -111,12 +109,12 @@ export async function consumableProvisionIcons() {
for (const match of ( for (const match of (
await readStringDVPL( await readStringDVPL(
`${DATA}/XML/item_defs/vehicles/common/provisions/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/provisions/list.xml`,
) )
).matchAll(listItemsPattern)) { ).matchAll(listItemsPattern)) {
const provisionsCommon = await readXMLDVPL<{ const provisionsCommon = await readXMLDVPL<{
root: ProvisionsCommon; root: ProvisionsCommon;
}>(`${DATA}/XML/item_defs/vehicles/common/provisions/${match[1]}.xml.dvpl`); }>(`${DATA}/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`);
await Promise.all( await Promise.all(
Object.values(provisionsCommon.root).map(async (provision) => { Object.values(provisionsCommon.root).map(async (provision) => {
const styleSheet = styleSheets.find((styleSheet) => const styleSheet = styleSheets.find((styleSheet) =>
@@ -137,9 +135,9 @@ export async function consumableProvisionIcons() {
.replace('.psd', '') .replace('.psd', '')
.replace('.txt', ''); .replace('.txt', '');
if (existsSync(`${DATA}/${configPath}.packed.webp.dvpl`)) { if (existsSync(`${DATA}/${configPath}.packed.webp`)) {
const image = sharp( const image = sharp(
await readDVPLFile(`${DATA}/${configPath}.packed.webp.dvpl`), await readDVPLFile(`${DATA}/${configPath}.packed.webp`),
); );
const content = await image.trim({ threshold: 100 }).toBuffer(); const content = await image.trim({ threshold: 100 }).toBuffer();
@@ -150,10 +148,10 @@ export async function consumableProvisionIcons() {
} else { } else {
const provisionsTexture = sharp( const provisionsTexture = sharp(
await readDVPLFile( await readDVPLFile(
`${`${DATA}/${configPath}`.split('/').slice(0, -1).join('/')}/texture0.packed.webp.dvpl`, `${`${DATA}/${configPath}`.split('/').slice(0, -1).join('/')}/texture0.packed.webp`,
), ),
); );
const sizes = (await readStringDVPL(`${DATA}/${configPath}.txt.dvpl`)) const sizes = (await readStringDVPL(`${DATA}/${configPath}.txt`))
.split('\n')[4] .split('\n')[4]
.split(' ') .split(' ')
.map(Number); .map(Number);
@@ -23,7 +23,7 @@ export async function currencies() {
await Promise.all( await Promise.all(
ICONS.map(async ([file, name]) => { ICONS.map(async ([file, name]) => {
const content = await sharp( const content = await sharp(
await readDVPLFile(`${DATA}/Gfx/Lobby/currency/${file}.dvpl`), await readDVPLFile(`${DATA}/Gfx/Lobby/currency/${file}`),
) )
.trim() .trim()
.toBuffer(); .toBuffer();
+47 -51
View File
@@ -7,7 +7,6 @@ import {
ConsumableTankCategoryFilterCategory, ConsumableTankCategoryFilterCategory,
Crew, Crew,
CrewType, CrewType,
DEFAULT_LOCALE,
encodePBBuffer, encodePBBuffer,
EquipmentDefinitions, EquipmentDefinitions,
EquipmentSlot, EquipmentSlot,
@@ -23,8 +22,6 @@ import {
ResearchCost, ResearchCost,
ShellType, ShellType,
SkillDefinitions, SkillDefinitions,
SUPPORTED_LOCALE_BLITZ_MAP,
SUPPORTED_LOCALES,
TankClass, TankClass,
TankDefinitions, TankDefinitions,
TankPrice, TankPrice,
@@ -34,6 +31,11 @@ import {
Unlock, Unlock,
Vector3, Vector3,
} from '@blitzkit/core'; } from '@blitzkit/core';
import {
DEFAULT_LOCALE,
SUPPORTED_LOCALE_BLITZ_MAP,
SUPPORTED_LOCALES,
} from '@blitzkit/i18n';
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { parse as parsePath } from 'path'; import { parse as parsePath } from 'path';
import { Vector3Tuple } from 'three'; import { Vector3Tuple } from 'three';
@@ -513,11 +515,9 @@ export async function definitions() {
console.log('Building definitions...'); console.log('Building definitions...');
const gameDefinitions: GameDefinitions = { const gameDefinitions: GameDefinitions = {
version: (await readStringDVPL(`${DATA}/version.txt.dvpl`)).split(' ')[0], version: (await readStringDVPL(`${DATA}/version.txt`)).split(' ')[0],
nations: ( nations: (
await readYAMLDVPL<AvailableNationsYaml>( await readYAMLDVPL<AvailableNationsYaml>(`${DATA}/available_nations.yaml`)
`${DATA}/available_nations.yaml.dvpl`,
)
).available_nations, ).available_nations,
gameModes: {}, gameModes: {},
roles: {}, roles: {},
@@ -550,26 +550,30 @@ export async function definitions() {
.then((response) => response.text()) .then((response) => response.text())
.then((string) => parseYaml(string) as BlitzStrings); .then((string) => parseYaml(string) as BlitzStrings);
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>( const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
`${DATA}/XML/item_defs/vehicles/common/optional_devices.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/optional_devices.xml`,
); );
const stringsI18n: Record<string, Record<string, string>> = {}; const stringsI18n: Record<string, Record<string, string>> = {};
for (const locale of SUPPORTED_LOCALES) { await Promise.all(
const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; SUPPORTED_LOCALES.map(async (locale) => {
const cache = await fetch( const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale];
`https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, const cache = await fetch(
) `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`,
.then((response) => response.text()) )
.then((string) => parseYaml(string) as BlitzStrings); .then((response) => response.text())
const preInstalled = await readYAMLDVPL<BlitzStrings>( .then((string) => parseYaml(string) as BlitzStrings);
`${DATA}/Strings/${blitzLocale}.yaml.dvpl`, const preInstalled = await readYAMLDVPL<BlitzStrings>(
); `${DATA}/Strings/${blitzLocale}.yaml`,
);
stringsI18n[locale] = { stringsI18n[locale] = {
...cache, ...cache,
...preInstalled, ...preInstalled,
}; };
}
console.log(`Loaded strings for ${locale}`);
}),
);
function getString(name: string) { function getString(name: string) {
const locales: Record<string, string> = { const locales: Record<string, string> = {
@@ -594,13 +598,13 @@ export async function definitions() {
const optionalDeviceSlots = await readXMLDVPL<{ const optionalDeviceSlots = await readXMLDVPL<{
root: OptionalDeviceSlots; root: OptionalDeviceSlots;
}>(`${DATA}/XML/item_defs/vehicles/common/optional_device_slots.xml.dvpl`); }>(`${DATA}/XML/item_defs/vehicles/common/optional_device_slots.xml`);
const consumables: ConsumablesCommon = {}; const consumables: ConsumablesCommon = {};
const provisions: ProvisionsCommon = {}; const provisions: ProvisionsCommon = {};
for (const match of ( for (const match of (
await readStringDVPL( await readStringDVPL(
`${DATA}/XML/item_defs/vehicles/common/consumables/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/consumables/list.xml`,
) )
).matchAll(/<items path="(.+)\.xml"\/>/g)) { ).matchAll(/<items path="(.+)\.xml"\/>/g)) {
if (match[1] === 'prototypes') continue; if (match[1] === 'prototypes') continue;
@@ -609,7 +613,7 @@ export async function definitions() {
consumables, consumables,
( (
await readXMLDVPL<{ root: ConsumablesCommon }>( await readXMLDVPL<{ root: ConsumablesCommon }>(
`${DATA}/XML/item_defs/vehicles/common/consumables/${match[1]}.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`,
) )
).root, ).root,
); );
@@ -617,7 +621,7 @@ export async function definitions() {
for (const match of ( for (const match of (
await readStringDVPL( await readStringDVPL(
`${DATA}/XML/item_defs/vehicles/common/provisions/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/provisions/list.xml`,
) )
).matchAll(/<items path="(.+)\.xml"\/>/g)) { ).matchAll(/<items path="(.+)\.xml"\/>/g)) {
if (match[1] === 'prototypes') continue; if (match[1] === 'prototypes') continue;
@@ -626,33 +630,33 @@ export async function definitions() {
provisions, provisions,
( (
await readXMLDVPL<{ root: ConsumablesCommon }>( await readXMLDVPL<{ root: ConsumablesCommon }>(
`${DATA}/XML/item_defs/vehicles/common/provisions/${match[1]}.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`,
) )
).root, ).root,
); );
} }
const avatar = await readXMLDVPL<{ root: Avatar }>( const avatar = await readXMLDVPL<{ root: Avatar }>(
`${DATA}/XML/item_defs/tankmen/avatar.xml.dvpl`, `${DATA}/XML/item_defs/tankmen/avatar.xml`,
); );
const maps = await readYAMLDVPL<Maps>(`${DATA}/maps.yaml.dvpl`); const maps = await readYAMLDVPL<Maps>(`${DATA}/maps.yaml`);
const tankXps = new Map<number, ResearchCost>(); const tankXps = new Map<number, ResearchCost>();
const camouflagesXml = await readXMLDVPL<{ root: CamouflagesXML }>( const camouflagesXml = await readXMLDVPL<{ root: CamouflagesXML }>(
`${DATA}/XML/item_defs/vehicles/common/camouflages.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/camouflages.xml`,
); );
const camouflagesYaml = await readYAMLDVPL<CamouflagesYaml>( const camouflagesYaml = await readYAMLDVPL<CamouflagesYaml>(
`${DATA}/camouflages.yaml.dvpl`, `${DATA}/camouflages.yaml`,
); );
const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages); const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages);
const squadBattleTypeStyles = await readYAMLDVPL<SquadBattleTypeStyles>( const squadBattleTypeStyles = await readYAMLDVPL<SquadBattleTypeStyles>(
`${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml.dvpl`, `${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`,
); );
const gameTypeSelectorStyles = await readYAMLDVPL<SquadBattleTypeStyles>( const gameTypeSelectorStyles = await readYAMLDVPL<SquadBattleTypeStyles>(
`${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml.dvpl`, `${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`,
); );
const gameModeNativeNames: Record<string, number> = {}; const gameModeNativeNames: Record<string, number> = {};
const combatRoles = await readYAMLDVPL<CombatRolesYaml>( const combatRoles = await readYAMLDVPL<CombatRolesYaml>(
`${DATA}/XML/item_defs/vehicles/common/combat_roles.yaml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/combat_roles.yaml`,
); );
const consumableNativeNames: Record<string, number> = {}; const consumableNativeNames: Record<string, number> = {};
const provisionNativeNames: Record<string, number> = {}; const provisionNativeNames: Record<string, number> = {};
@@ -702,29 +706,23 @@ export async function definitions() {
await Promise.all( await Promise.all(
nations.map(async (nation) => { nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
); );
const turretList = await readXMLDVPL<{ const turretList = await readXMLDVPL<{
root: TurretDefinitionsList; root: TurretDefinitionsList;
}>( }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/turrets.xml`);
`${DATA}/XML/item_defs/vehicles/${nation}/components/turrets.xml.dvpl`,
);
const gunList = await readXMLDVPL<{ const gunList = await readXMLDVPL<{
root: GunDefinitionsList; root: GunDefinitionsList;
}>(`${DATA}/XML/item_defs/vehicles/${nation}/components/guns.xml.dvpl`); }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/guns.xml`);
const shellList = await readXMLDVPL<{ const shellList = await readXMLDVPL<{
root: ShellDefinitionsList; root: ShellDefinitionsList;
}>(`${DATA}/XML/item_defs/vehicles/${nation}/components/shells.xml.dvpl`); }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/shells.xml`);
const enginesList = await readXMLDVPL<{ const enginesList = await readXMLDVPL<{
root: EngineDefinitionsList; root: EngineDefinitionsList;
}>( }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/engines.xml`);
`${DATA}/XML/item_defs/vehicles/${nation}/components/engines.xml.dvpl`,
);
const chassisList = await readXMLDVPL<{ const chassisList = await readXMLDVPL<{
root: ChassisDefinitionsList; root: ChassisDefinitionsList;
}>( }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/chassis.xml`);
`${DATA}/XML/item_defs/vehicles/${nation}/components/chassis.xml.dvpl`,
);
function resolveUnlocks(unlocks?: BlitzModuleType) { function resolveUnlocks(unlocks?: BlitzModuleType) {
if (!unlocks) return []; if (!unlocks) return [];
@@ -776,10 +774,10 @@ export async function definitions() {
const tank = tankList.root[tankKey]; const tank = tankList.root[tankKey];
let tankPrice: TankPrice; let tankPrice: TankPrice;
const tankDefinition = await readXMLDVPL<{ root: VehicleDefinitions }>( const tankDefinition = await readXMLDVPL<{ root: VehicleDefinitions }>(
`${DATA}/XML/item_defs/vehicles/${nation}/${tankKey}.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/${nation}/${tankKey}.xml`,
); );
const tankParameters = await readYAMLDVPL<TankParameters>( const tankParameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml.dvpl`, `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
); );
const turretOrigin = tankDefinition.root.hull.turretPositions.turret const turretOrigin = tankDefinition.root.hull.turretPositions.turret
.split(' ') .split(' ')
@@ -1667,8 +1665,6 @@ export async function definitions() {
}; };
}); });
return;
await commitAssets('definitions', [ await commitAssets('definitions', [
{ {
content: encodePBBuffer(GameDefinitions, gameDefinitions), content: encodePBBuffer(GameDefinitions, gameDefinitions),
@@ -22,14 +22,14 @@ export async function equipmentIcons() {
const changes: FileChange[] = []; const changes: FileChange[] = [];
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>( const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
`${DATA}/XML/item_defs/vehicles/common/optional_devices.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/common/optional_devices.xml`,
); );
const mappings = await readYAMLDVPL<Mappings>( const mappings = await readYAMLDVPL<Mappings>(
`${DATA}/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml.dvpl`, `${DATA}/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`,
); );
const image = sharp( const image = sharp(
await readDVPLFile( await readDVPLFile(
`${DATA}/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp.dvpl`, `${DATA}/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`,
), ),
); );
@@ -57,7 +57,7 @@ export async function equipmentIcons() {
if (configPath.startsWith('Gfx/Lobby')) { if (configPath.startsWith('Gfx/Lobby')) {
const configPathWebp = configPath.replace('.txt', ''); const configPathWebp = configPath.replace('.txt', '');
const image = sharp( const image = sharp(
await readDVPLFile(`${DATA}/${configPathWebp}.packed.webp.dvpl`), await readDVPLFile(`${DATA}/${configPathWebp}.packed.webp`),
); );
const content = await image.trim().toBuffer(); const content = await image.trim().toBuffer();
@@ -66,7 +66,7 @@ export async function equipmentIcons() {
content, content,
}); });
} else { } else {
const config = await readStringDVPL(`${DATA}/${configPath}.dvpl`); const config = await readStringDVPL(`${DATA}/${configPath}`);
const sizes = config.split('\n')[4].split(' ').map(Number); const sizes = config.split('\n')[4].split(' ').map(Number);
const content = await image const content = await image
.clone() .clone()
+3 -4
View File
@@ -14,7 +14,7 @@ export async function flags() {
.filter( .filter(
(flag) => (flag) =>
flag.startsWith('flag_profile-stat_') && flag.startsWith('flag_profile-stat_') &&
!flag.endsWith('@2x.packed.webp.dvpl'), !flag.endsWith('@2x.packed.webp'),
) )
.map(async (flag) => { .map(async (flag) => {
const image = sharp( const image = sharp(
@@ -34,7 +34,7 @@ export async function flags() {
.filter( .filter(
(flag) => (flag) =>
flag.startsWith('flag_tutor-tank_') && flag.startsWith('flag_tutor-tank_') &&
!flag.endsWith('@2x.packed.webp.dvpl'), !flag.endsWith('@2x.packed.webp'),
) )
.map(async (flag) => { .map(async (flag) => {
const content = await readDVPLFile(`${DATA}/Gfx/Lobby/flags/${flag}`); const content = await readDVPLFile(`${DATA}/Gfx/Lobby/flags/${flag}`);
@@ -50,8 +50,7 @@ export async function flags() {
(await readdir(`${DATA}/Gfx/Lobby/flags`)) (await readdir(`${DATA}/Gfx/Lobby/flags`))
.filter( .filter(
(flag) => (flag) =>
flag.startsWith('flag_filter_') && flag.startsWith('flag_filter_') && flag.endsWith('@2x.packed.webp'),
flag.endsWith('@2x.packed.webp.dvpl'),
) )
.map(async (flag) => { .map(async (flag) => {
const content = await sharp( const content = await sharp(
@@ -11,10 +11,10 @@ export async function gameModeBanners() {
const changes: FileChange[] = []; const changes: FileChange[] = [];
const gameTypeSelectorStyles = await readYAMLDVPL<SquadBattleTypeStyles>( const gameTypeSelectorStyles = await readYAMLDVPL<SquadBattleTypeStyles>(
`${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml.dvpl`, `${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`,
); );
const squadBattleTypeStyles = await readYAMLDVPL<SquadBattleTypeStyles>( const squadBattleTypeStyles = await readYAMLDVPL<SquadBattleTypeStyles>(
`${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml.dvpl`, `${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`,
); );
const bannerMatches: { name: string; path: string }[] = []; const bannerMatches: { name: string; path: string }[] = [];
@@ -38,7 +38,7 @@ export async function gameModeBanners() {
} }
const content = await sharp( const content = await sharp(
await readDVPLFile(`${DATA}${path}.packed.webp.dvpl`), await readDVPLFile(`${DATA}${path}.packed.webp`),
) )
.trim({ background: { r: 0, g: 0, b: 0, alpha: 0 } }) .trim({ background: { r: 0, g: 0, b: 0, alpha: 0 } })
.toBuffer(); .toBuffer();
@@ -12,10 +12,10 @@ export async function moduleIcons() {
(await readdir(`${DATA}/Gfx/UI/ModulesTechTree`)) (await readdir(`${DATA}/Gfx/UI/ModulesTechTree`))
.filter( .filter(
(file) => (file) =>
!file.endsWith('@2x.packed.webp.dvpl') && file.startsWith('vehicle'), !file.endsWith('@2x.packed.webp') && file.startsWith('vehicle'),
) )
.map(async (file) => { .map(async (file) => {
const nameRaw = file.match(/vehicle(.+)\.packed\.webp\.dvpl/)![1]; const nameRaw = file.match(/vehicle(.+)\.packed\.webp/)![1];
const name = nameRaw[0].toLowerCase() + nameRaw.slice(1); const name = nameRaw[0].toLowerCase() + nameRaw.slice(1);
const content = await sharp( const content = await sharp(
await readDVPLFile(`${DATA}/Gfx/UI/ModulesTechTree/${file}`), await readDVPLFile(`${DATA}/Gfx/UI/ModulesTechTree/${file}`),
@@ -11,15 +11,15 @@ export async function shellIcons() {
const image = sharp( const image = sharp(
await readDVPLFile( await readDVPLFile(
`${DATA}/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp.dvpl`, `${DATA}/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`,
), ),
); );
const changes = await Promise.all( const changes = await Promise.all(
(await readdir(`${DATA}/Gfx/Shared/tank-supply/ammunition/big`)) (await readdir(`${DATA}/Gfx/Shared/tank-supply/ammunition/big`))
.filter((file) => file.endsWith('_l.txt.dvpl')) .filter((file) => file.endsWith('_l.txt'))
.map(async (file) => { .map(async (file) => {
const name = file.match(/(.+)_l\.txt\.dvpl/)![1]; const name = file.match(/(.+)_l\.txt/)![1];
const sizes = ( const sizes = (
await readStringDVPL( await readStringDVPL(
`${DATA}/Gfx/Shared/tank-supply/ammunition/big/${file}`, `${DATA}/Gfx/Shared/tank-supply/ammunition/big/${file}`,
@@ -29,13 +29,13 @@ export async function skillIcons() {
console.log('Building skill icons...'); console.log('Building skill icons...');
const avatar = await readXMLDVPL<{ root: Avatar }>( const avatar = await readXMLDVPL<{ root: Avatar }>(
`${DATA}/XML/item_defs/tankmen/avatar.xml.dvpl`, `${DATA}/XML/item_defs/tankmen/avatar.xml`,
); );
const changes = await Promise.all( const changes = await Promise.all(
Object.values(avatar.root.skills).map(async (skill) => { Object.values(avatar.root.skills).map(async (skill) => {
const icon = Array.isArray(skill.icon) ? skill.icon[0] : skill.icon; const icon = Array.isArray(skill.icon) ? skill.icon[0] : skill.icon;
const name = icon.name.split('/').at(-1)!.replace(/_\d$/, ''); const name = icon.name.split('/').at(-1)!.replace(/_\d$/, '');
const path = `${DATA}${icon.name.replace('~res:', '')}.packed.webp.dvpl`; const path = `${DATA}${icon.name.replace('~res:', '')}.packed.webp`;
const image = sharp(await readDVPLFile(path)).trim(); const image = sharp(await readDVPLFile(path)).trim();
const content = await image.toBuffer(); const content = await image.toBuffer();
@@ -20,7 +20,7 @@ export async function tankArmors() {
await Promise.all( await Promise.all(
nations.map(async (nation) => { nations.map(async (nation) => {
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
); );
await Promise.all( await Promise.all(
@@ -53,7 +53,7 @@ export async function tankIcons() {
await Promise.all( await Promise.all(
nations.map(async (nation) => { nations.map(async (nation) => {
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
); );
await Promise.all( await Promise.all(
@@ -63,14 +63,14 @@ export async function tankIcons() {
const nationVehicleId = tank.id; const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
const parameters = await readYAMLDVPL<TankParameters>( const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml.dvpl`, `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
); );
const smallPath = `${DATA}/${parameters.resourcesPath.smallIconPath const smallPath = `${DATA}/${parameters.resourcesPath.smallIconPath
.replace(/~res:\//, '') .replace(/~res:\//, '')
.replace(/\..+/, '')}.packed.webp.dvpl`; .replace(/\..+/, '')}.packed.webp`;
const bigPath = `${DATA}/${parameters.resourcesPath.bigIconPath const bigPath = `${DATA}/${parameters.resourcesPath.bigIconPath
.replace(/~res:\//, '') .replace(/~res:\//, '')
.replace(/\..+/, '')}.packed.webp.dvpl`; .replace(/\..+/, '')}.packed.webp`;
const big = await sharp(await readDVPLFile(bigPath)) const big = await sharp(await readDVPLFile(bigPath))
.trim() .trim()
.toBuffer(); .toBuffer();
@@ -23,7 +23,7 @@ export async function tankModels() {
const nation = nations[nationIndex]; const nation = nations[nationIndex];
const changes: FileChange[] = []; const changes: FileChange[] = [];
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml.dvpl`, `${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
); );
console.log(`Building models for ${nation}`); console.log(`Building models for ${nation}`);
@@ -35,7 +35,7 @@ export async function tankModels() {
const id = toUniqueId(nation, tank.id); const id = toUniqueId(nation, tank.id);
const parameters = await readYAMLDVPL<TankParameters>( const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml.dvpl`, `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
); );
const model = await extractModel( const model = await extractModel(
DATA, DATA,
@@ -13,8 +13,8 @@ import {
import { readDVPLFile } from './readDVPLFile'; import { readDVPLFile } from './readDVPLFile';
export async function extractArmor(data: string, fileName: string) { export async function extractArmor(data: string, fileName: string) {
const sc2Path = `${data}/3d/Tanks/CollisionMeshes/${fileName}.sc2.dvpl`; const sc2Path = `${data}/3d/Tanks/CollisionMeshes/${fileName}.sc2`;
const scgPath = `${data}/3d/Tanks/CollisionMeshes/${fileName}.scg.dvpl`; const scgPath = `${data}/3d/Tanks/CollisionMeshes/${fileName}.scg`;
const sc2 = new Sc2ReadStream( const sc2 = new Sc2ReadStream(
(await readDVPLFile(sc2Path)).buffer as ArrayBuffer, (await readDVPLFile(sc2Path)).buffer as ArrayBuffer,
).sc2(); ).sc2();
@@ -26,8 +26,8 @@ const omitMeshNames = {
}; };
export async function extractModel(data: string, path: string) { export async function extractModel(data: string, path: string) {
const sc2Path = `${data}/3d/${path}.sc2.dvpl`; const sc2Path = `${data}/3d/${path}.sc2`;
const scgPath = `${data}/3d/${path}.scg.dvpl`; const scgPath = `${data}/3d/${path}.scg`;
const sc2 = new Sc2ReadStream( const sc2 = new Sc2ReadStream(
(await readDVPLFile(sc2Path)).buffer as ArrayBuffer, (await readDVPLFile(sc2Path)).buffer as ArrayBuffer,
).sc2(); ).sc2();
@@ -1,6 +1,11 @@
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
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) {
return readDVPL(await readFile(file)); if (DATA_COMPRESSED) {
return readDVPL(await readFile(`${file}.dvpl`));
}
return readFile(file);
} }
@@ -5,7 +5,7 @@ import { readDVPLFile } from '../readDVPLFile';
import { TextureMutation } from './constants'; import { TextureMutation } from './constants';
export async function readTexture(path: string, mutation?: TextureMutation) { export async function readTexture(path: string, mutation?: TextureMutation) {
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl'); const ddsTexturePath = path.replace('.tex', '.dx11.dds');
const isDds = existsSync(ddsTexturePath); const isDds = existsSync(ddsTexturePath);
const resolvedTexturePath = isDds const resolvedTexturePath = isDds
? ddsTexturePath ? ddsTexturePath
+2 -2
View File
@@ -2,13 +2,13 @@
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/core'; import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
export default defineConfig({ export default defineConfig({
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: 'en',
locales: SUPPORTED_LOCALES, locales: [...SUPPORTED_LOCALES],
routing: { routing: {
prefixDefaultLocale: false, prefixDefaultLocale: false,
}, },
@@ -1,6 +1,7 @@
import { asset } from '@blitzkit/core'; import { asset } from '@blitzkit/core';
import { Flex, Heading, IconButton, Table } from '@radix-ui/themes'; import { Flex, Heading, IconButton, Table } from '@radix-ui/themes';
import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics'; import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral'; import { CompareEphemeral } from '../../stores/compareEphemeral';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell'; import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
import { CompareRow } from './CompareRow'; import { CompareRow } from './CompareRow';
@@ -15,6 +16,7 @@ export function Body({ stats }: BodyProps) {
const hasNonRegularGun = members.some( const hasNonRegularGun = members.some(
({ gun }) => gun.gun_type!.$case !== 'regular', ({ gun }) => gun.gun_type!.$case !== 'regular',
); );
const { unwrap } = useLocale();
const mutateCompareEphemeral = CompareEphemeral.useMutation(); const mutateCompareEphemeral = CompareEphemeral.useMutation();
return ( return (
@@ -60,7 +62,7 @@ export function Body({ stats }: BodyProps) {
}} }}
> >
<img <img
alt={thisShell.name} alt={unwrap(thisShell.name)}
width={16} width={16}
height={16} height={16}
src={asset(`icons/shells/${thisShell.icon}.webp`)} src={asset(`icons/shells/${thisShell.icon}.webp`)}
@@ -9,6 +9,7 @@ import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDe
import { awaitableSkillDefinitions } from '../../core/awaitables/skillDefinitions'; import { awaitableSkillDefinitions } from '../../core/awaitables/skillDefinitions';
import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics'; import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
import { Var } from '../../core/radix/var'; import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral'; import { CompareEphemeral } from '../../stores/compareEphemeral';
import type { EquipmentMatrix } from '../../stores/duel'; import type { EquipmentMatrix } from '../../stores/duel';
import { BlitzkitButtonGrayIcon } from '../BlitzkitButtonGrayIcon'; import { BlitzkitButtonGrayIcon } from '../BlitzkitButtonGrayIcon';
@@ -43,6 +44,7 @@ export function CompareTable({ stats }: CompareTableProps) {
const crewSkills = CompareEphemeral.use((state) => state.crewSkills); const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
const members = CompareEphemeral.use((state) => state.members); const members = CompareEphemeral.use((state) => state.members);
const mutateCompareEphemeral = CompareEphemeral.useMutation(); const mutateCompareEphemeral = CompareEphemeral.useMutation();
const { unwrap } = useLocale();
return ( return (
<StickyTableRoot <StickyTableRoot
@@ -293,10 +295,10 @@ export function CompareTable({ stats }: CompareTableProps) {
<Flex direction="column" justify="center"> <Flex direction="column" justify="center">
{provisions.map((provision, index) => ( {provisions.map((provision, index) => (
<img <img
alt={ alt={unwrap(
provisionDefinitions.provisions[provision] provisionDefinitions.provisions[provision]
.name .name,
} )}
key={provision} key={provision}
src={asset( src={asset(
`/icons/provisions/${provision}.webp`, `/icons/provisions/${provision}.webp`,
@@ -473,11 +475,11 @@ export function CompareTable({ stats }: CompareTableProps) {
<Flex direction="column" justify="center"> <Flex direction="column" justify="center">
{consumables.map((consumable, index) => ( {consumables.map((consumable, index) => (
<img <img
alt={ alt={unwrap(
consumableDefinitions.consumables[ consumableDefinitions.consumables[
consumable consumable
].name ].name,
} )}
key={consumable} key={consumable}
src={asset( src={asset(
`/icons/consumables/${consumable}.webp`, `/icons/consumables/${consumable}.webp`,
@@ -2,6 +2,7 @@ import { type TankDefinition, tankIcon } from '@blitzkit/core';
import { Flex, Text } from '@radix-ui/themes'; import { Flex, Text } from '@radix-ui/themes';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { Vector2 } from 'three'; import { Vector2 } from 'three';
import { useLocale } from '../../hooks/useLocale';
import { CompareEphemeral } from '../../stores/compareEphemeral'; import { CompareEphemeral } from '../../stores/compareEphemeral';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell'; import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
import { InsertionMarker } from './IntersectionMarker'; import { InsertionMarker } from './IntersectionMarker';
@@ -16,6 +17,7 @@ interface TankCardProps {
export function TankCard({ index, tank }: TankCardProps) { export function TankCard({ index, tank }: TankCardProps) {
const draggable = useRef<HTMLDivElement>(null); const draggable = useRef<HTMLDivElement>(null);
const mutateCompareEphemeral = CompareEphemeral.useMutation(); const mutateCompareEphemeral = CompareEphemeral.useMutation();
const { unwrap } = useLocale();
useEffect(() => { useEffect(() => {
const initial = new Vector2(); const initial = new Vector2();
@@ -118,7 +120,7 @@ export function TankCard({ index, tank }: TankCardProps) {
}} }}
> >
<img <img
alt={tank.name} alt={unwrap(tank.name)}
src={tankIcon(tank.id)} src={tankIcon(tank.id)}
width={64} width={64}
height={64} height={64}
@@ -136,7 +138,7 @@ export function TankCard({ index, tank }: TankCardProps) {
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
}} }}
> >
{tank.name} {unwrap(tank.name)}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
@@ -3,7 +3,7 @@ import {
type AccountListItem, type AccountListItem,
type AccountListWithServer, type AccountListWithServer,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en.json'; import strings from '@blitzkit/i18n/strings/en.json';
import { CaretLeftIcon, Link2Icon, PersonIcon } from '@radix-ui/react-icons'; import { CaretLeftIcon, Link2Icon, PersonIcon } from '@radix-ui/react-icons';
import { import {
Badge, Badge,
@@ -8,7 +8,7 @@ import {
sumCompositeStats, sumCompositeStats,
type IndividualTankStats, type IndividualTankStats,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en.json'; import strings from '@blitzkit/i18n/strings/en.json';
import { ReloadIcon } from '@radix-ui/react-icons'; import { ReloadIcon } from '@radix-ui/react-icons';
import { ContextMenu } from '@radix-ui/themes'; import { ContextMenu } from '@radix-ui/themes';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE } from '@blitzkit/core'; import { DEFAULT_LOCALE } from '@blitzkit/i18n';
import { GlobeIcon } from '@radix-ui/react-icons'; import { GlobeIcon } from '@radix-ui/react-icons';
import { Flex, Text } from '@radix-ui/themes'; import { Flex, Text } from '@radix-ui/themes';
import type { LocaleAcceptorProps } from '../../hooks/useLocale'; import type { LocaleAcceptorProps } from '../../hooks/useLocale';
+1 -1
View File
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE } from '@blitzkit/core'; 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'; import type { LocaleAcceptorProps } from '../hooks/useLocale';
@@ -1,9 +1,10 @@
import {} from '@blitzkit/core';
import { import {
DEFAULT_LOCALE, DEFAULT_LOCALE,
localizedStrings,
SUPPORTED_LOCALE_FLAGS,
SUPPORTED_LOCALES, SUPPORTED_LOCALES,
} from '@blitzkit/core'; SUPPORTED_LOCALE_FLAGS,
localizedStrings,
} from '@blitzkit/i18n';
import { Select } from '@radix-ui/themes'; import { Select } from '@radix-ui/themes';
import type { LocaleAcceptorProps } from '../hooks/useLocale'; import type { LocaleAcceptorProps } from '../hooks/useLocale';
@@ -3,6 +3,7 @@ import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
import { Flex, Text } from '@radix-ui/themes'; import { Flex, Text } from '@radix-ui/themes';
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions'; import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
import { useDelta } from '../../hooks/useDelta'; import { useDelta } from '../../hooks/useDelta';
import { useLocale } from '../../hooks/useLocale';
import { GenericTankComponentButton } from './GenericTankComponentButton'; import { GenericTankComponentButton } from './GenericTankComponentButton';
import type { TankComponentButtonProps } from './TankComponentButton'; import type { TankComponentButtonProps } from './TankComponentButton';
@@ -22,11 +23,12 @@ export function ConsumableButton({
}: ConsumableButtonProps) { }: ConsumableButtonProps) {
const cooldownDelta = useDelta(cooldown ?? 0); const cooldownDelta = useDelta(cooldown ?? 0);
const durationDelta = useDelta(duration ?? 0); const durationDelta = useDelta(duration ?? 0);
const { unwrap } = useLocale();
return ( return (
<GenericTankComponentButton <GenericTankComponentButton
style={{ width: '6rem' }} style={{ width: '6rem' }}
tooltip={consumableDefinitions.consumables[consumable].name} tooltip={unwrap(consumableDefinitions.consumables[consumable].name)}
icon={asset(`icons/consumables/${consumable}.webp`)} icon={asset(`icons/consumables/${consumable}.webp`)}
{...props} {...props}
> >
@@ -1,5 +1,6 @@
import { asset } from '@blitzkit/core'; import { asset } from '@blitzkit/core';
import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions'; import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions';
import { useLocale } from '../../hooks/useLocale';
import { GenericTankComponentButton } from './GenericTankComponentButton'; import { GenericTankComponentButton } from './GenericTankComponentButton';
import type { TankComponentButtonProps } from './TankComponentButton'; import type { TankComponentButtonProps } from './TankComponentButton';
@@ -10,9 +11,11 @@ interface EquipmentButtonProps extends TankComponentButtonProps {
const equipmentDefinitions = await awaitableEquipmentDefinitions; const equipmentDefinitions = await awaitableEquipmentDefinitions;
export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
const { unwrap } = useLocale();
return ( return (
<GenericTankComponentButton <GenericTankComponentButton
tooltip={equipmentDefinitions.equipments[equipment].name} tooltip={unwrap(equipmentDefinitions.equipments[equipment].name)}
icon={asset(`icons/equipment/${equipment}.webp`)} icon={asset(`icons/equipment/${equipment}.webp`)}
{...props} {...props}
/> />
@@ -1,5 +1,6 @@
import { asset } from '@blitzkit/core'; import { asset } from '@blitzkit/core';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { useLocale } from '../../hooks/useLocale';
import { GenericTankComponentButton } from './GenericTankComponentButton'; import { GenericTankComponentButton } from './GenericTankComponentButton';
import type { TankComponentButtonProps } from './TankComponentButton'; import type { TankComponentButtonProps } from './TankComponentButton';
@@ -10,9 +11,11 @@ interface ProvisionButtonProps extends TankComponentButtonProps {
const provisionDefinitions = await awaitableProvisionDefinitions; const provisionDefinitions = await awaitableProvisionDefinitions;
export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) { export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
const { unwrap } = useLocale();
return ( return (
<GenericTankComponentButton <GenericTankComponentButton
tooltip={provisionDefinitions.provisions[provision].name} tooltip={unwrap(provisionDefinitions.provisions[provision].name)}
icon={asset(`icons/provisions/${provision}.webp`)} icon={asset(`icons/provisions/${provision}.webp`)}
{...props} {...props}
/> />
@@ -9,6 +9,7 @@ import {
} from '@blitzkit/core'; } from '@blitzkit/core';
import { DropdownMenu, Flex } from '@radix-ui/themes'; import { DropdownMenu, Flex } from '@radix-ui/themes';
import { useState } from 'react'; import { useState } from 'react';
import { useLocale } from '../hooks/useLocale';
import { ModuleButton } from './ModuleButtons/ModuleButton'; import { ModuleButton } from './ModuleButtons/ModuleButton';
interface ModuleManagerProps { interface ModuleManagerProps {
@@ -35,6 +36,7 @@ export function ModuleManager({
const [gunMenuOpen, setGunMenuOpen] = useState(false); const [gunMenuOpen, setGunMenuOpen] = useState(false);
const [engineMenuOpen, setEngineMenuOpen] = useState(false); const [engineMenuOpen, setEngineMenuOpen] = useState(false);
const [trackMenuOpen, setTrackMenuOpen] = useState(false); const [trackMenuOpen, setTrackMenuOpen] = useState(false);
const { unwrap } = useLocale();
return ( return (
<Flex gap="1" wrap="wrap"> <Flex gap="1" wrap="wrap">
@@ -71,7 +73,7 @@ export function ModuleManager({
module="turret" module="turret"
discriminator={TIER_ROMAN_NUMERALS[turret.tier]} discriminator={TIER_ROMAN_NUMERALS[turret.tier]}
/>{' '} />{' '}
{turret.name} {unwrap(turret.name)}
</DropdownMenu.RadioItem> </DropdownMenu.RadioItem>
); );
})} })}
@@ -117,7 +119,7 @@ export function ModuleManager({
TIER_ROMAN_NUMERALS[gun.gun_type!.value.base.tier] TIER_ROMAN_NUMERALS[gun.gun_type!.value.base.tier]
} }
/>{' '} />{' '}
{gun.gun_type!.value.base.name} {unwrap(gun.gun_type!.value.base.name)}
</DropdownMenu.RadioItem> </DropdownMenu.RadioItem>
); );
})} })}
@@ -156,7 +158,7 @@ export function ModuleManager({
module="engine" module="engine"
discriminator={TIER_ROMAN_NUMERALS[engine.tier]} discriminator={TIER_ROMAN_NUMERALS[engine.tier]}
/>{' '} />{' '}
{engine.name} {unwrap(engine.name)}
</DropdownMenu.RadioItem> </DropdownMenu.RadioItem>
); );
})} })}
@@ -195,7 +197,7 @@ export function ModuleManager({
module="chassis" module="chassis"
discriminator={TIER_ROMAN_NUMERALS[track.tier]} discriminator={TIER_ROMAN_NUMERALS[track.tier]}
/>{' '} />{' '}
{track.name} {unwrap(track.name)}
</DropdownMenu.RadioItem> </DropdownMenu.RadioItem>
); );
})} })}
@@ -28,7 +28,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
}: TankCardProps, }: TankCardProps,
ref, ref,
) => { ) => {
const { locale } = useLocale(); const { unwrap, locale } = useLocale();
const provideLink = !noLink && onSelect === undefined; const provideLink = !noLink && onSelect === undefined;
const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const Icon = classIcons[tank.class]; const Icon = classIcons[tank.class];
@@ -73,7 +73,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
}} }}
> >
<img <img
alt={tank.name} alt={unwrap(tank.name)}
src={asset(`icons/tanks/blitzkit/${tank.id}.webp`)} src={asset(`icons/tanks/blitzkit/${tank.id}.webp`)}
className="image" className="image"
draggable={false} draggable={false}
@@ -89,7 +89,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
> >
<Icon className="class-icon" /> <Icon className="class-icon" />
<Text align="center" className="name"> <Text align="center" className="name">
{tank.name} {unwrap(tank.name)}
</Text> </Text>
</Flex> </Flex>
@@ -10,7 +10,7 @@ interface TankRowHeaderCellProps {
} }
export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) { export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
const { locale } = useLocale(); const { locale, unwrap } = useLocale();
const Icon = classIcons[tank.class]; const Icon = classIcons[tank.class];
return ( return (
@@ -24,7 +24,7 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
tabIndex={-1} tabIndex={-1}
> >
<img <img
alt={tank.name} alt={unwrap(tank.name)}
draggable={false} draggable={false}
src={tankIcon(tank.id)} src={tankIcon(tank.id)}
style={{ style={{
@@ -70,7 +70,7 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
<ExperimentIcon style={{ width: '1em', height: '1em' }} /> <ExperimentIcon style={{ width: '1em', height: '1em' }} />
)} )}
{tank.name} {unwrap(tank.name)}
</LinkI18n> </LinkI18n>
</StickyRowHeaderCell> </StickyRowHeaderCell>
); );
@@ -12,7 +12,7 @@ interface QuickLinkProps {
export function QuickLink({ topResult }: QuickLinkProps) { export function QuickLink({ topResult }: QuickLinkProps) {
const tankFilters = useStore($tankFilters); const tankFilters = useStore($tankFilters);
const { locale } = useLocale(); const { locale, unwrap } = useLocale();
if (!tankFilters.search || !topResult || tankFilters.searching) return null; if (!tankFilters.search || !topResult || tankFilters.searching) return null;
@@ -20,7 +20,7 @@ export function QuickLink({ topResult }: QuickLinkProps) {
<TextField.Slot> <TextField.Slot>
<LinkI18n locale={locale} href={`/tools/tankopedia/${topResult.id}`}> <LinkI18n locale={locale} href={`/tools/tankopedia/${topResult.id}`}>
<Button variant="ghost"> <Button variant="ghost">
{topResult.name} <CaretRightIcon /> {unwrap(topResult.name)} <CaretRightIcon />
</Button> </Button>
</LinkI18n> </LinkI18n>
</TextField.Slot> </TextField.Slot>
@@ -27,6 +27,7 @@ import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/pr
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics'; import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
import { useEquipment } from '../../../../../hooks/useEquipment'; import { useEquipment } from '../../../../../hooks/useEquipment';
import { useLocale } from '../../../../../hooks/useLocale';
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition'; import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../stores/duel'; import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
@@ -39,6 +40,7 @@ const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
]); ]);
export function Characteristics() { export function Characteristics() {
const { unwrap } = useLocale();
const mutateDuel = Duel.useMutation(); const mutateDuel = Duel.useMutation();
const crewSkills = TankopediaEphemeral.use((state) => state.skills); const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const penetrationDistanceInput = useRef<HTMLInputElement>(null); const penetrationDistanceInput = useRef<HTMLInputElement>(null);
@@ -146,7 +148,7 @@ export function Characteristics() {
<Flex> <Flex>
{gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => ( {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => (
<Tooltip content={thisShell.name} key={thisShell.id}> <Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
<IconButton <IconButton
color={thisShell.id === shell.id ? undefined : 'gray'} color={thisShell.id === shell.id ? undefined : 'gray'}
variant="soft" variant="soft"
@@ -172,7 +174,7 @@ export function Characteristics() {
}} }}
> >
<img <img
alt={thisShell.name} alt={unwrap(thisShell.name)}
width={16} width={16}
height={16} height={16}
src={asset(`icons/shells/${thisShell.icon}.webp`)} src={asset(`icons/shells/${thisShell.icon}.webp`)}
@@ -103,7 +103,7 @@ function ModuleButton({
gap={isTank ? '1' : undefined} gap={isTank ? '1' : undefined}
> >
<Text size="1" color="gray"> <Text size="1" color="gray">
{formatCompact(unlock.cost.value)} {formatCompact(locale, unlock.cost.value)}
</Text> </Text>
<img <img
@@ -3,6 +3,7 @@ import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions'; import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions'; import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { useLocale } from '../../hooks/useLocale';
import { Duel } from '../../stores/duel'; import { Duel } from '../../stores/duel';
const [gameDefinitions, consumableDefinitions, provisionDefinitions] = const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
@@ -13,6 +14,7 @@ const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
]); ]);
export function GameModeSection() { export function GameModeSection() {
const { unwrap } = useLocale();
const tank = Duel.use((state) => state.protagonist.tank); const tank = Duel.use((state) => state.protagonist.tank);
const roles = Object.entries(tank.roles); const roles = Object.entries(tank.roles);
@@ -67,7 +69,7 @@ export function GameModeSection() {
justify="center" justify="center"
> >
<Text size="4" weight="bold"> <Text size="4" weight="bold">
{gameMode.name} {unwrap(gameMode.name)}
</Text> </Text>
<Flex direction="column"> <Flex direction="column">
@@ -84,9 +86,9 @@ export function GameModeSection() {
objectFit: 'contain', objectFit: 'contain',
}} }}
src={asset(`icons/provisions/${id}.webp`)} src={asset(`icons/provisions/${id}.webp`)}
alt={provisions.name} alt={unwrap(provisions.name)}
/> />
{provisions.name} {unwrap(provisions.name)}
</Flex> </Flex>
</Text> </Text>
); );
@@ -104,9 +106,9 @@ export function GameModeSection() {
objectFit: 'contain', objectFit: 'contain',
}} }}
src={asset(`icons/consumables/${id}.webp`)} src={asset(`icons/consumables/${id}.webp`)}
alt={consumable.name} alt={unwrap(consumable.name)}
/> />
{consumable.name} {unwrap(consumable.name)}
</Flex> </Flex>
</Text> </Text>
); );
@@ -89,7 +89,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const { strings } = useLocale(); const { strings, unwrap } = useLocale();
return ( return (
<> <>
@@ -244,7 +244,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
}} }}
> >
<img <img
alt={thisShell.name} alt={unwrap(thisShell.name)}
src={asset(`icons/shells/${thisShell.icon}.webp`)} src={asset(`icons/shells/${thisShell.icon}.webp`)}
style={{ style={{
width: '50%', width: '50%',
@@ -362,7 +362,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
<Button variant="ghost"> <Button variant="ghost">
<Flex gap="2" align="center"> <Flex gap="2" align="center">
<SmallTankIcon id={antagonistTank.id} size={16} /> <SmallTankIcon id={antagonistTank.id} size={16} />
{antagonistTank.name} {unwrap(antagonistTank.name)}
</Flex> </Flex>
</Button> </Button>
</Dialog.Trigger> </Dialog.Trigger>
@@ -6,6 +6,7 @@ import { NAVBAR_HEIGHT } from '../../../constants/navbar';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { Var } from '../../../core/radix/var'; import { Var } from '../../../core/radix/var';
import { useFullScreen } from '../../../hooks/useFullScreen'; import { useFullScreen } from '../../../hooks/useFullScreen';
import { useLocale } from '../../../hooks/useLocale';
import { Duel } from '../../../stores/duel'; import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
@@ -18,6 +19,7 @@ import { TankSandboxLoader } from './components/TankSandboxLoader';
const tankDefinitions = await awaitableTankDefinitions; const tankDefinitions = await awaitableTankDefinitions;
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
const { unwrap } = useLocale();
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const isFullScreen = useFullScreen(); const isFullScreen = useFullScreen();
const protagonist = Duel.use((state) => state.protagonist.tank); const protagonist = Duel.use((state) => state.protagonist.tank);
@@ -117,7 +119,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
wrap="nowrap" wrap="nowrap"
color={treeColor} color={treeColor}
> >
{protagonist.name} {unwrap(protagonist.name)}
</Heading> </Heading>
</Flex> </Flex>
@@ -9,6 +9,7 @@ import {
Text, Text,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { useState } from 'react'; import { useState } from 'react';
import { useLocale } from '../../../../../../hooks/useLocale';
import { App } from '../../../../../../stores/app'; import { App } from '../../../../../../stores/app';
import { Duel } from '../../../../../../stores/duel'; import { Duel } from '../../../../../../stores/duel';
import { Stars } from '../../Stars'; import { Stars } from '../../Stars';
@@ -24,6 +25,7 @@ export function VoteCaster() {
const [postVote, setPostVote] = useState<boolean | null>(null); const [postVote, setPostVote] = useState<boolean | null>(null);
// true = open, false = closed, null = casting // true = open, false = closed, null = casting
const [showDialogue, setShowDialogue] = useState<boolean | null>(false); const [showDialogue, setShowDialogue] = useState<boolean | null>(false);
const { unwrap } = useLocale();
return ( return (
<> <>
@@ -35,7 +37,7 @@ export function VoteCaster() {
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content maxWidth="23rem"> <Dialog.Content maxWidth="23rem">
<Dialog.Title>Voting for {tank.name}</Dialog.Title> <Dialog.Title>Voting for {unwrap(tank.name)}</Dialog.Title>
<Flex direction="column" mt="4"> <Flex direction="column" mt="4">
<Flex align="center" justify="between"> <Flex align="center" justify="between">
@@ -1,6 +1,7 @@
import { formatCompact } from '@blitzkit/core'; import { formatCompact } from '@blitzkit/core';
import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes'; import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes';
import { memo } from 'react'; import { memo } from 'react';
import { useLocale } from '../../../../../hooks/useLocale';
import { useTankVotes } from '../../../../../hooks/useTankVotes'; import { useTankVotes } from '../../../../../hooks/useTankVotes';
import { App } from '../../../../../stores/app'; import { App } from '../../../../../stores/app';
import { Duel } from '../../../../../stores/duel'; import { Duel } from '../../../../../stores/duel';
@@ -13,13 +14,14 @@ export const Votes = memo(
const id = Duel.use((state) => state.protagonist.tank.id); const id = Duel.use((state) => state.protagonist.tank.id);
const votes = useTankVotes(id); const votes = useTankVotes(id);
const wargaming = App.use((state) => state.logins.wargaming); const wargaming = App.use((state) => state.logins.wargaming);
const { locale } = useLocale();
return ( return (
<Flex direction="column" gap="3"> <Flex direction="column" gap="3">
<Flex justify="between" align="center"> <Flex justify="between" align="center">
<Heading size="4"> <Heading size="4">
{!votes && <Skeleton height="1em" width="4rem" />} {!votes && <Skeleton height="1em" width="4rem" />}
{votes && formatCompact(votes.votes)}{' '} {votes && formatCompact(locale, votes.votes)}{' '}
{votes && (votes.votes > 1 ? 'votes' : 'vote')} {votes && (votes.votes > 1 ? 'votes' : 'vote')}
</Heading> </Heading>
@@ -38,7 +38,7 @@ export function MetaSection() {
const mutateDuel = Duel.useMutation(); const mutateDuel = Duel.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const [showSwapDialog, setShowSwapDialog] = useState(false); const [showSwapDialog, setShowSwapDialog] = useState(false);
const { locale, strings } = useLocale(); const { locale, strings, unwrap } = useLocale();
return ( return (
<Flex justify="center" align="center"> <Flex justify="center" align="center">
@@ -124,7 +124,7 @@ export function MetaSection() {
<Flex direction="column" width="100%"> <Flex direction="column" width="100%">
{protagonist.name_full && ( {protagonist.name_full && (
<Listing label={strings.website.tools.tankopedia.meta.full_name}> <Listing label={strings.website.tools.tankopedia.meta.full_name}>
{protagonist.name_full} {unwrap(protagonist.name_full)}
</Listing> </Listing>
)} )}
<Listing label={strings.website.tools.tankopedia.meta.nation}> <Listing label={strings.website.tools.tankopedia.meta.nation}>
@@ -20,7 +20,7 @@ const [tankDefinitions, averageDefinitions] = await Promise.all([
]); ]);
export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
const { locale, strings } = useLocale(); const { locale, strings, unwrap } = useLocale();
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier); const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
const tank = tankDefinitions.tanks[id]; const tank = tankDefinitions.tanks[id];
const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]); const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]);
@@ -78,7 +78,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
}} }}
> >
<img <img
alt={tank.name} alt={unwrap(tank.name)}
src={asset(`icons/tanks/big/${id}.webp`)} src={asset(`icons/tanks/big/${id}.webp`)}
width={64} width={64}
height={64} height={64}
@@ -92,7 +92,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
<Text color="gray" size="1"> <Text color="gray" size="1">
{TIER_ROMAN_NUMERALS[tank.tier]} {TIER_ROMAN_NUMERALS[tank.tier]}
</Text> </Text>
<Text wrap="nowrap">{tank.name}</Text> <Text wrap="nowrap">{unwrap(tank.name)}</Text>
</Flex> </Flex>
<Flex gap="2" align="center"> <Flex gap="2" align="center">
<Text color="gray" size="1" wrap="nowrap"> <Text color="gray" size="1" wrap="nowrap">
@@ -6,13 +6,14 @@ import {
type CompositeStats, type CompositeStats,
type CompositeStatsKey, type CompositeStatsKey,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en.json'; 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';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { breakdownConfig } from '../constants/embeds'; import { breakdownConfig } from '../constants/embeds';
import { toRadiusVar } from '../core/radix/utils'; import { toRadiusVar } from '../core/radix/utils';
import { useLocale } from '../hooks/useLocale';
import { useEmbedStateCurry } from '../stores/embedState/utilities'; import { useEmbedStateCurry } from '../stores/embedState/utilities';
import { ClassHeavy } from './ClassIcon/components/ClassHeavy'; import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
import { ClassLight } from './ClassIcon/components/ClassLight'; import { ClassLight } from './ClassIcon/components/ClassLight';
@@ -52,6 +53,7 @@ export function BreakdownEmbedCard({
}: BreakdownEmbedCardProps) { }: BreakdownEmbedCardProps) {
const { useEmbedState, useRichText } = const { useEmbedState, useRichText } =
useEmbedStateCurry<typeof breakdownConfig>(); useEmbedStateCurry<typeof breakdownConfig>();
const { unwrap } = useLocale();
return ( return (
<Flex <Flex
@@ -111,7 +113,7 @@ export function BreakdownEmbedCard({
? 'Total' ? 'Total'
: tank === undefined : tank === undefined
? `Unknown tank` ? `Unknown tank`
: tank.name} : unwrap(tank.name)}
</Flex> </Flex>
</Text> </Text>
</Flex> </Flex>
@@ -1,4 +1,5 @@
import { assertSecret, DEFAULT_LOCALE, localizedStrings } from '@blitzkit/core'; import { assertSecret } from '@blitzkit/core';
import { DEFAULT_LOCALE, localizedStrings } from '@blitzkit/i18n';
export function resolveBranchName(locale: string = DEFAULT_LOCALE) { export function resolveBranchName(locale: string = DEFAULT_LOCALE) {
const strings = localizedStrings[locale]; const strings = localizedStrings[locale];
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/core'; import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/i18n';
export function getBestLocale() { export function getBestLocale() {
const desiredLocale = const desiredLocale =
+1 -1
View File
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE, localizedStrings } from '@blitzkit/core'; import { DEFAULT_LOCALE, localizedStrings } from '@blitzkit/i18n';
export function getStrings(locale: string = DEFAULT_LOCALE) { export function getStrings(locale: string = DEFAULT_LOCALE) {
return localizedStrings[locale]; return localizedStrings[locale];
+3 -2
View File
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE } from '@blitzkit/core'; import { DEFAULT_LOCALE, unwrapper } from '@blitzkit/i18n';
import { createContext, useContext, type ReactNode } from 'react'; import { createContext, useContext, type ReactNode } from 'react';
import { getStrings } from '../core/i18n/getStrings'; import { getStrings } from '../core/i18n/getStrings';
@@ -29,8 +29,9 @@ export function useLocale() {
} }
const strings = getStrings(context.locale); const strings = getStrings(context.locale);
const unwrap = unwrapper(context.locale);
return { locale: context.locale, strings }; return { locale: context.locale, strings, unwrap };
} }
export interface LocaleAcceptorProps { export interface LocaleAcceptorProps {
@@ -6,7 +6,7 @@ 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/core'; import { DEFAULT_LOCALE } from '@blitzkit/i18n';
export interface Props { export interface Props {
noFooter?: boolean; noFooter?: boolean;
@@ -40,7 +40,7 @@ const isRoot = pathname === '/';
<Party3 /> <Party3 />
<script> <script>
import { SUPPORTED_LOCALES } from '@blitzkit/core'; import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { getBestLocale } from '../core/i18n/getBestLocale'; import { getBestLocale } from '../core/i18n/getBestLocale';
const isI18nPath = SUPPORTED_LOCALES.some((locale) => const isI18nPath = SUPPORTED_LOCALES.some((locale) =>
@@ -51,8 +51,6 @@ const isRoot = pathname === '/';
window.location.pathname.startsWith(`/${locale}`), window.location.pathname.startsWith(`/${locale}`),
); );
console.log(bestLocale);
if (bestLocale !== currentPathLocale) { if (bestLocale !== currentPathLocale) {
window.location.pathname = isI18nPath window.location.pathname = isI18nPath
? window.location.pathname.replace( ? window.location.pathname.replace(
@@ -3,7 +3,7 @@ import { App } from '../../../../stores/app';
export const AUTH_PROVIDERS = ['wargaming'] as const; export const AUTH_PROVIDERS = ['wargaming'] as const;
type AuthProvider = (typeof AUTH_PROVIDERS)[number]; export type AuthProvider = (typeof AUTH_PROVIDERS)[number];
export const AUTH_PROVIDER_NAMES: Record<AuthProvider, string> = { export const AUTH_PROVIDER_NAMES: Record<AuthProvider, string> = {
wargaming: 'Wargaming', wargaming: 'Wargaming',
@@ -4,7 +4,12 @@ import type { GetStaticPaths } from 'astro';
import { PageWrapper } from '../../../../components/PageWrapper'; import { PageWrapper } from '../../../../components/PageWrapper';
import BaseLayout from '../../../../layouts/BaseLayout.astro'; import BaseLayout from '../../../../layouts/BaseLayout.astro';
import { getStaticPaths as parentGetStaticPaths } from '../../index.astro'; import { getStaticPaths as parentGetStaticPaths } from '../../index.astro';
import { AUTH_PROVIDERS, AUTH_PROVIDER_NAMES, Authorize } from './_index'; import {
AUTH_PROVIDER_NAMES,
AUTH_PROVIDERS,
Authorize,
type AuthProvider,
} from './_index';
export const getStaticPaths = (() => { export const getStaticPaths = (() => {
const parentPaths = parentGetStaticPaths(); const parentPaths = parentGetStaticPaths();
@@ -14,7 +19,7 @@ export const getStaticPaths = (() => {
).flat(); ).flat();
}) satisfies GetStaticPaths; }) satisfies GetStaticPaths;
const { provider } = Astro.params; const provider = Astro.params.provider as AuthProvider;
--- ---
<BaseLayout> <BaseLayout>
@@ -1,9 +1,6 @@
--- ---
import { import { assertSecret } from '@blitzkit/core';
assertSecret, import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/i18n';
DEFAULT_LOCALE,
SUPPORTED_LOCALES,
} from '@blitzkit/core';
import { Box, Flex, Heading } from '@radix-ui/themes'; import { Box, Flex, Heading } from '@radix-ui/themes';
import type { GetStaticPaths } from 'astro'; import type { GetStaticPaths } from 'astro';
import { AesonPlug } from '../../components/AesonPlug'; import { AesonPlug } from '../../components/AesonPlug';
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE } from '@blitzkit/core'; import { DEFAULT_LOCALE } from '@blitzkit/i18n';
import { Flex, Switch, Text } from '@radix-ui/themes'; import { Flex, Switch, Text } from '@radix-ui/themes';
import { LocaleSwitcher } from '../../../components/LocaleSwitcher'; import { LocaleSwitcher } from '../../../components/LocaleSwitcher';
import { PageWrapper } from '../../../components/PageWrapper'; import { PageWrapper } from '../../../components/PageWrapper';
@@ -13,7 +13,7 @@ import {
type IndividualAccountInfo, type IndividualAccountInfo,
type IndividualTankStats, type IndividualTankStats,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en.json'; import strings from '@blitzkit/i18n/strings/en.json';
import { import {
ArrowDownIcon, ArrowDownIcon,
MagnifyingGlassIcon, MagnifyingGlassIcon,
@@ -4,7 +4,6 @@ import { CalloutsSection } from '../../../../../components/Tankopedia/CalloutsSe
import { CharacteristicsSection } from '../../../../../components/Tankopedia/CharacteristicsSection'; import { CharacteristicsSection } from '../../../../../components/Tankopedia/CharacteristicsSection';
import { GameModeSection } from '../../../../../components/Tankopedia/GameModeSection'; import { GameModeSection } from '../../../../../components/Tankopedia/GameModeSection';
import { HeroSection } from '../../../../../components/Tankopedia/HeroSection'; import { HeroSection } from '../../../../../components/Tankopedia/HeroSection';
import { HistorySection } from '../../../../../components/Tankopedia/HistorySection';
import { MetaSection } from '../../../../../components/Tankopedia/MetaSection'; import { MetaSection } from '../../../../../components/Tankopedia/MetaSection';
import { TechTreeSection } from '../../../../../components/Tankopedia/TechTreeSection'; import { TechTreeSection } from '../../../../../components/Tankopedia/TechTreeSection';
import { VideoSection } from '../../../../../components/Tankopedia/VideoSection'; import { VideoSection } from '../../../../../components/Tankopedia/VideoSection';
@@ -3,7 +3,6 @@ import {
asset, asset,
createDefaultProvisions, createDefaultProvisions,
createDefaultSkills, createDefaultSkills,
DEFAULT_LOCALE,
fetchEquipmentDefinitions, fetchEquipmentDefinitions,
fetchModelDefinitions, fetchModelDefinitions,
fetchProvisionDefinitions, fetchProvisionDefinitions,
@@ -11,6 +10,7 @@ import {
fetchTankDefinitions, fetchTankDefinitions,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '@blitzkit/core'; } from '@blitzkit/core';
import { DEFAULT_LOCALE, unwrapper } from '@blitzkit/i18n';
import type { GetStaticPaths } from 'astro'; import type { GetStaticPaths } from 'astro';
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics'; import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
import { getStrings } from '../../../../../core/i18n/getStrings'; import { getStrings } from '../../../../../core/i18n/getStrings';
@@ -44,8 +44,9 @@ const tankModelDefinitions = await fetchModelDefinitions();
const tank = tankDefinitions.tanks[id]; const tank = tankDefinitions.tanks[id];
const tankModelDefinition = tankModelDefinitions.models[tank.id]; const tankModelDefinition = tankModelDefinitions.models[tank.id];
const name = tank.name_full ?? tank.name; const name = tank.name_full ?? tank.name;
const unwrap = unwrapper(locale);
const title = literals(strings.website.tools.tankopedia.tank_seo_title, [ const title = literals(strings.website.tools.tankopedia.tank_seo_title, [
name, unwrap(name),
TIER_ROMAN_NUMERALS[tank.tier], TIER_ROMAN_NUMERALS[tank.tier],
strings.common.nations_adjectives[ strings.common.nations_adjectives[
tank.nation as keyof typeof strings.common.nations_adjectives tank.nation as keyof typeof strings.common.nations_adjectives
@@ -84,7 +85,7 @@ const characteristics = tankCharacteristics(
const description = literals( const description = literals(
strings.website.tools.tankopedia.tank_seo_description, strings.website.tools.tankopedia.tank_seo_description,
[ [
name, unwrap(name),
Math.round(characteristics.dpm).toLocaleString(locale), Math.round(characteristics.dpm).toLocaleString(locale),
Math.round(characteristics.damage).toLocaleString(locale), Math.round(characteristics.damage).toLocaleString(locale),
Math.round(characteristics.penetration).toLocaleString(locale), Math.round(characteristics.penetration).toLocaleString(locale),
@@ -1,4 +1,4 @@
import { DEFAULT_LOCALE } from '@blitzkit/core'; import { DEFAULT_LOCALE } from '@blitzkit/i18n';
import type { APIRoute, GetStaticPaths } from 'astro'; import type { APIRoute, GetStaticPaths } from 'astro';
import { fetchGlossary } from '../../../core/blitz/fetchGlossary'; import { fetchGlossary } from '../../../core/blitz/fetchGlossary';
@@ -1,4 +1,4 @@
import en from '@blitzkit/core/lang/en.json'; import en from '@blitzkit/i18n/strings/en.json';
import type { TankPerformanceSortType } from '.'; import type { TankPerformanceSortType } from '.';
export const TankPerformanceSortTypeNamesArray = Object.keys( export const TankPerformanceSortTypeNamesArray = Object.keys(
@@ -1,4 +1,4 @@
import type en from '@blitzkit/core/lang/en.json'; import type en from '@blitzkit/i18n/strings/en.json';
import { create } from 'zustand'; import { create } from 'zustand';
import { createContextualStore } from '../../core/zustand/createContextualStore'; import { createContextualStore } from '../../core/zustand/createContextualStore';
@@ -1,4 +1,4 @@
import type en from '@blitzkit/core/lang/en.json'; import type en from '@blitzkit/i18n/strings/en.json';
import * as lodash from 'lodash-es'; import * as lodash from 'lodash-es';
import { create } from 'zustand'; import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware'; import { persist, subscribeWithSelector } from 'zustand/middleware';