From ef9778ea96f9963e5152cb2b2a7579255a3dab00 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:22:41 -0600 Subject: [PATCH] feat: localize game assets --- .vscode/settings.json | 1 + bun.lockb | Bin 421992 -> 422363 bytes packages/bot/package.json | 1 + .../bot/src/commands/breakdown/render.tsx | 7 +- packages/bot/src/commands/ownedTanks.tsx | 4 +- packages/bot/src/commands/replay.tsx | 16 ++- packages/bot/src/commands/research.tsx | 24 ++--- .../bot/src/core/discord/autocompleteTanks.ts | 19 +++- .../core/localization/strings/constants.ts | 2 +- .../bot/src/core/localization/translator.ts | 6 +- packages/bot/tsconfig.json | 2 +- packages/core/src/blitzkit/index.ts | 1 - packages/core/src/blitzkit/tankNames.ts | 9 +- packages/core/src/index.ts | 1 + .../core/src/protos/camouflage_definitions.ts | 2 +- .../core/src/protos/consumable_definitions.ts | 2 +- .../core/src/protos/equipment_definitions.ts | 2 +- packages/core/src/protos/game_definitions.ts | 2 +- packages/core/src/protos/i18n.ts | 2 +- packages/core/src/protos/map_definitions.ts | 2 +- .../core/src/protos/provision_definitions.ts | 2 +- packages/core/src/protos/tank_definitions.ts | 2 +- packages/i18n/package.json | 8 +- packages/i18n/src/index.ts | 2 + packages/i18n/src/strings.ts | 8 +- .../blitzkit => i18n/src}/supportedLocales.ts | 0 packages/i18n/src/unwrapper.ts | 12 +++ packages/prate/package.json | 9 -- packages/scripts/src/applyPatches.ts | 8 +- .../scripts/src/buildAssets/boosterIcons.ts | 6 +- .../src/buildAssets/camouflageIcons.ts | 2 +- packages/scripts/src/buildAssets/constants.ts | 34 ++++-- .../buildAssets/consumableProvisionIcons.ts | 28 +++-- .../scripts/src/buildAssets/currencies.ts | 2 +- .../scripts/src/buildAssets/definitions.ts | 98 +++++++++--------- .../scripts/src/buildAssets/equipmentIcons.ts | 10 +- packages/scripts/src/buildAssets/flags.ts | 7 +- .../src/buildAssets/gameModeBanners.ts | 6 +- .../scripts/src/buildAssets/moduleIcons.ts | 4 +- .../scripts/src/buildAssets/shellIcons.ts | 6 +- .../scripts/src/buildAssets/skillIcons.ts | 4 +- .../scripts/src/buildAssets/tankArmors.ts | 2 +- packages/scripts/src/buildAssets/tankIcons.ts | 8 +- .../scripts/src/buildAssets/tankModels.ts | 4 +- .../scripts/src/core/blitz/extractArmor.ts | 4 +- .../src/core/blitz/extractModel/index.ts | 4 +- .../scripts/src/core/blitz/readDVPLFile.ts | 7 +- .../src/core/blitz/readTexture/index.ts | 2 +- packages/website/astro.config.mjs | 4 +- .../website/src/components/Compare/Body.tsx | 4 +- .../src/components/Compare/CompareTable.tsx | 14 +-- .../src/components/Compare/TankCard.tsx | 6 +- .../src/components/Embeds/GenerateURL.tsx | 2 +- .../src/components/Embeds/breakdown.tsx | 2 +- .../components/Home/HomeLocaleSwitcher.tsx | 2 +- packages/website/src/components/LinkI18n.tsx | 2 +- .../website/src/components/LocaleSwitcher.tsx | 7 +- .../ModuleButtons/ConsumableButton.tsx | 4 +- .../ModuleButtons/EquipmentButton.tsx | 5 +- .../ModuleButtons/ProvisionButton.tsx | 5 +- .../website/src/components/ModuleManager.tsx | 10 +- .../website/src/components/TankCard/index.tsx | 6 +- .../src/components/TankRowHeaderCell.tsx | 6 +- .../TankSearch/components/QuickLink.tsx | 4 +- .../components/Characteristics/index.tsx | 6 +- .../components/Modules.tsx | 2 +- .../components/Tankopedia/GameModeSection.tsx | 12 ++- .../HeroSection/components/Options/index.tsx | 6 +- .../Tankopedia/HeroSection/index.tsx | 4 +- .../Votes/components/VoteCaster.tsx | 4 +- .../MetaSection/components/Votes/index.tsx | 4 +- .../Tankopedia/MetaSection/index.tsx | 4 +- .../TechTreeSection/components/Node.tsx | 6 +- .../website/src/components/TanksEmbed.tsx | 6 +- .../website/src/core/blitzkit/branches.ts | 3 +- .../website/src/core/i18n/getBestLocale.ts | 2 +- packages/website/src/core/i18n/getStrings.ts | 2 +- packages/website/src/hooks/useLocale.tsx | 5 +- packages/website/src/layouts/BaseLayout.astro | 6 +- .../[...locale]/auth/[provider]/_index.tsx | 2 +- .../[...locale]/auth/[provider]/index.astro | 9 +- .../website/src/pages/[...locale]/index.astro | 7 +- .../src/pages/[...locale]/settings/_index.tsx | 2 +- .../[...locale]/tools/session/_index.tsx | 2 +- .../tools/tankopedia/[id]/_index.tsx | 1 - .../tools/tankopedia/[id]/index.astro | 7 +- .../src/pages/api/gallery/[avatar].webp.ts | 2 +- .../stores/tankPerformanceSort/constants.ts | 2 +- .../src/stores/tankPerformanceSort/index.ts | 2 +- .../src/stores/tankopediaPersistent/index.ts | 2 +- 90 files changed, 336 insertions(+), 251 deletions(-) rename packages/{core/src/blitzkit => i18n/src}/supportedLocales.ts (100%) create mode 100644 packages/i18n/src/unwrapper.ts delete mode 100644 packages/prate/package.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 6be5a5ada..7817465ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -189,6 +189,7 @@ "unpickled", "Unregistering", "unrotate", + "unwrapper", "vercel", "vickers", "vitepress", diff --git a/bun.lockb b/bun.lockb index b09f501d1d8e2d7221ba198ea44aec66a3e56eb0..350daac174f9c69759cae8d4950322d8480bed28 100644 GIT binary patch delta 75340 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 => { .addStringOption(addUsernameChoices), async handler(interaction) { - const { t, translate } = translator(interaction.locale); + const { t, translate, unwrap } = translator(interaction.locale); const tier = Number(interaction.options.getString('tier')); const { id, region } = await resolvePlayerFromCommand(interaction); const accountInfo = await getAccountInfo(region, id); @@ -77,7 +77,7 @@ export const ownedTanksCommand = new Promise((resolve) => { ) .map( (tank) => - `[${flags[tank.nation]} ${markdownEscape(tank.name)}]()`, + `[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}]()`, ); const lines = [title, ...tankList]; diff --git a/packages/bot/src/commands/replay.tsx b/packages/bot/src/commands/replay.tsx index c8dacfce8..d7af17ca5 100644 --- a/packages/bot/src/commands/replay.tsx +++ b/packages/bot/src/commands/replay.tsx @@ -6,6 +6,7 @@ import { tankIcon, TIER_ROMAN_NUMERALS, } from '@blitzkit/core'; +import { Locale } from 'discord.js'; import { clamp } from 'lodash-es'; import { CommandWrapper } from '../components/CommandWrapper'; import { TitleBar } from '../components/TitleBar'; @@ -18,6 +19,7 @@ import { RenderConfiguration } from '../core/blitzkit/renderConfiguration'; import { blitzStarsTankAverages } from '../core/blitzstars/tankAverages'; import { buttonLink } from '../core/discord/buttonLink'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; import { theme } from '../stitches.config'; @@ -218,11 +220,13 @@ function stat(value: string | number, flex = 2) { } async function playerListing( + locale: Locale, player: WotInspectorReplayPlayerData, protagonistTeam: boolean, winningTeam: number, recordingPlayer = false, ) { + const { unwrap } = translator(locale); const awaitedTankAverages = await blitzStarsTankAverages; const awaitedTankDefinitions = await tankDefinitions; const tank = awaitedTankDefinitions.tanks[player.vehicle_descr]; @@ -275,7 +279,7 @@ async function playerListing( }} > {tank.name} - {tank.name} + {unwrap(tank.name)} @@ -530,6 +534,7 @@ export const replayCommand = new Promise((resolve) => { ) .map((player) => playerListing( + interaction.locale, player, true, replayData.winner_team, @@ -553,7 +558,12 @@ export const replayCommand = new Promise((resolve) => { players .filter((player) => player.team === antagonistTeamId) .map((player) => - playerListing(player, false, replayData.winner_team), + playerListing( + interaction.locale, + player, + false, + replayData.winner_team, + ), ), )} diff --git a/packages/bot/src/commands/research.tsx b/packages/bot/src/commands/research.tsx index 5e3925279..4826ba516 100644 --- a/packages/bot/src/commands/research.tsx +++ b/packages/bot/src/commands/research.tsx @@ -77,7 +77,7 @@ export const researchCommand = new Promise((resolve) => { .addStringOption(addUsernameChoices), async handler(interaction) { - const { t, translate } = translator(interaction.locale); + const { t, translate, unwrap } = translator(interaction.locale); const awaitedTankDefinitions = await tankDefinitions; const targetTankId = await resolveTankId( interaction.options.getString('target-tank', true), @@ -99,7 +99,7 @@ export const researchCommand = new Promise((resolve) => { if (targetTankId === startingTankId) { 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((resolve) => { if (startingTank.tier > targetTank.tier) { return translate('bot.commands.research.errors.unordered', [ - escapeMarkdown(startingTank.name), - escapeMarkdown(targetTank.name), - escapeMarkdown(targetTank.name), + escapeMarkdown(unwrap(startingTank.name)), + escapeMarkdown(unwrap(targetTank.name)), + escapeMarkdown(unwrap(targetTank.name)), ]); } if (!targetTankAncestry.includes(startingTankId)) { return translate('bot.commands.research.errors.tanks_not_on_line', [ - escapeMarkdown(targetTank.name), - escapeMarkdown(startingTank.name), + escapeMarkdown(unwrap(targetTank.name)), + escapeMarkdown(unwrap(startingTank.name)), ]); } } else { if (targetTank.type !== TankType.RESEARCHABLE) { return translate('bot.commands.research.errors.non_tech_tree', [ - escapeMarkdown(targetTank.name), + escapeMarkdown(unwrap(targetTank.name)), ]); } if (targetTank.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((resolve) => { if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { return translate('bot.commands.research.errors.already_researched', [ - escapeMarkdown(targetTank.name), + escapeMarkdown(unwrap(targetTank.name)), ]); } @@ -452,7 +452,7 @@ export const researchCommand = new Promise((resolve) => { }} > {tank.name}((resolve) => { color: theme.colors.textHighContrast, }} > - {tank.name} + {unwrap(tank.name)} diff --git a/packages/bot/src/core/discord/autocompleteTanks.ts b/packages/bot/src/core/discord/autocompleteTanks.ts index 7acafcb94..8fb029a7b 100644 --- a/packages/bot/src/core/discord/autocompleteTanks.ts +++ b/packages/bot/src/core/discord/autocompleteTanks.ts @@ -1,15 +1,17 @@ import { fetchTankNames, TankType } from '@blitzkit/core'; +import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; import { ApplicationCommandOptionChoiceData, AutocompleteInteraction, CacheType, } from 'discord.js'; import { go } from 'fuzzysort'; +import { tankNames } from '../blitzkit/nonBlockingPromises'; +import { translator } from '../localization/translator'; import { DISCORD_CHOICES_MAX_NAME_SIZE, OVERFLOW_SUFFIX, } from './autocompleteClan/constants'; -import { tankNames } from '../blitzkit/nonBlockingPromises'; export const tankNamesTechTreeOnly = fetchTankNames().then((names) => names.filter((tank) => tank.treeType === TankType.RESEARCHABLE), @@ -20,6 +22,7 @@ export async function autocompleteTanks( techTreeOnly = false, fields = ['tank'], ) { + const { unwrap } = translator(interaction.locale); const focusedOption = interaction.options.getFocused(true); if (!fields.includes(focusedOption.name)) return; @@ -30,18 +33,24 @@ export async function autocompleteTanks( focusedOption.value, await (techTreeOnly ? tankNamesTechTreeOnly : tankNames), { - keys: ['searchableName', 'searchableNameDeburr', 'camouflages'], + keys: [ + ...SUPPORTED_LOCALES.map((locale) => [ + `searchableName.${locale}`, + `searchableNameDeburr.${locale}`, + ]).flat(), + 'camouflages', + ], limit: 10, }, ).map(async (item) => { - let name = item.obj.searchableName; + let name = unwrap(item.obj.searchableName); if (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, - item.obj.searchableName.length - + unwrap(item.obj.searchableName).length - overSize - OVERFLOW_SUFFIX.length, )}${OVERFLOW_SUFFIX}`; diff --git a/packages/bot/src/core/localization/strings/constants.ts b/packages/bot/src/core/localization/strings/constants.ts index a6eae19c1..34c47c27f 100644 --- a/packages/bot/src/core/localization/strings/constants.ts +++ b/packages/bot/src/core/localization/strings/constants.ts @@ -1,4 +1,4 @@ -import { DEFAULT_LOCALE, SupportedLocale } from '@blitzkit/core'; +import { DEFAULT_LOCALE, SupportedLocale } from '@blitzkit/i18n'; import { Locale } from 'discord.js'; const SUPPORTED_LOCALES_DISCORD_MAP: Record = { diff --git a/packages/bot/src/core/localization/translator.ts b/packages/bot/src/core/localization/translator.ts index 2b09fb8b5..93a68bc70 100644 --- a/packages/bot/src/core/localization/translator.ts +++ b/packages/bot/src/core/localization/translator.ts @@ -1,8 +1,10 @@ +import { unwrapper } from '@blitzkit/i18n'; import { Locale } from 'discord.js'; import { translations } from './strings'; import { DEFAULT_LOCALE_DISCORD, SUPPORTED_LOCALES_DISCORD, + SUPPORTED_LOCALES_DISCORD_MAP_INVERSE, } from './strings/constants'; export function translator(localeRaw: Locale) { @@ -74,5 +76,7 @@ export function translator(localeRaw: Locale) { .join(''); } - return { locale, translate, t }; + const unwrap = unwrapper(SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]); + + return { locale, translate, t, unwrap }; } diff --git a/packages/bot/tsconfig.json b/packages/bot/tsconfig.json index e86b8943b..d24722b0d 100644 --- a/packages/bot/tsconfig.json +++ b/packages/bot/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts"] + "include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"] } diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index 3f3cf0daa..983def4d7 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -46,5 +46,4 @@ export * from './votes'; export * from './createDefaultProvisions'; export * from './metaSortTank'; export * from './romanize'; -export * from './supportedLocales'; export * from './youtubers'; diff --git a/packages/core/src/blitzkit/tankNames.ts b/packages/core/src/blitzkit/tankNames.ts index c871ea40c..b5b53c183 100644 --- a/packages/core/src/blitzkit/tankNames.ts +++ b/packages/core/src/blitzkit/tankNames.ts @@ -1,4 +1,5 @@ import { deburr } from 'lodash-es'; +import { I18nString } from '../protos'; import { fetchCamouflageDefinitions } from './camouflageDefinitions'; import { fetchTankDefinitions } from './tankDefinitions'; @@ -11,12 +12,18 @@ export async function fetchTankNames() { return await Promise.all( tankDefinitionsArray.map(async (tank) => { + const searchableNameDeburr: I18nString = { locales: {} }; + + Object.entries(tank.name_full ?? tank.name).forEach(([key, value]) => { + searchableNameDeburr.locales[key] = deburr(value); + }); + return { id: tank.id, name: tank.name, nameFull: tank.name_full, searchableName: tank.name_full ?? tank.name, - searchableNameDeburr: deburr(tank.name_full ?? tank.name), + searchableNameDeburr, camouflages: tank.camouflages ?.map((id) => camouflageDefinitions.camouflages[id]?.name) .filter(Boolean) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 653e639b5..cae496f70 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,7 @@ export * from './blitz'; export * from './blitzkit'; export * from './databases'; +export * from './imgur'; export * from './math'; export * from './patreon'; export * from './protobuf'; diff --git a/packages/core/src/protos/camouflage_definitions.ts b/packages/core/src/protos/camouflage_definitions.ts index 659f1aeb0..02d3e846d 100644 --- a/packages/core/src/protos/camouflage_definitions.ts +++ b/packages/core/src/protos/camouflage_definitions.ts @@ -6,7 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/core/src/protos/consumable_definitions.ts b/packages/core/src/protos/consumable_definitions.ts index c6e636d8d..27bbbe6d3 100644 --- a/packages/core/src/protos/consumable_definitions.ts +++ b/packages/core/src/protos/consumable_definitions.ts @@ -6,7 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/core/src/protos/equipment_definitions.ts b/packages/core/src/protos/equipment_definitions.ts index 5583cd976..9742df57c 100644 --- a/packages/core/src/protos/equipment_definitions.ts +++ b/packages/core/src/protos/equipment_definitions.ts @@ -6,7 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/core/src/protos/game_definitions.ts b/packages/core/src/protos/game_definitions.ts index 81af446a6..5cfe6cf12 100644 --- a/packages/core/src/protos/game_definitions.ts +++ b/packages/core/src/protos/game_definitions.ts @@ -6,7 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/core/src/protos/i18n.ts b/packages/core/src/protos/i18n.ts index 21d5ca7f0..ceea83dc8 100644 --- a/packages/core/src/protos/i18n.ts +++ b/packages/core/src/protos/i18n.ts @@ -18,7 +18,7 @@ export interface I18nString_LocalesEntry { value: string; } -function createBaseI18nString(): I18nString { +export function createBaseI18nString(): I18nString { return { locales: {} }; } diff --git a/packages/core/src/protos/map_definitions.ts b/packages/core/src/protos/map_definitions.ts index 35fa0de9f..08b505919 100644 --- a/packages/core/src/protos/map_definitions.ts +++ b/packages/core/src/protos/map_definitions.ts @@ -6,7 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/core/src/protos/provision_definitions.ts b/packages/core/src/protos/provision_definitions.ts index 7026c5ab4..3fc9a5e41 100644 --- a/packages/core/src/protos/provision_definitions.ts +++ b/packages/core/src/protos/provision_definitions.ts @@ -7,7 +7,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { ConsumableTankFilter } from "./consumable_definitions"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/core/src/protos/tank_definitions.ts b/packages/core/src/protos/tank_definitions.ts index 12c79ca20..01fd44dae 100644 --- a/packages/core/src/protos/tank_definitions.ts +++ b/packages/core/src/protos/tank_definitions.ts @@ -6,7 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; -import { I18nString } from "./i18n"; +import { I18nString, createBaseI18nString } from "./i18n"; export const protobufPackage = "blitzkit"; diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 5704ae2e9..3dd28cd05 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -3,10 +3,6 @@ "private": true, "type": "module", "main": "src/index.ts", - "dependencies": { - }, - "devDependencies": { - "@types/adm-zip": "^0.5.5", - "@types/lz4js": "^0.2.1" - } + "dependencies": {}, + "devDependencies": {} } diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index 916b6e9f8..a403c6a03 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -1 +1,3 @@ export * from './strings'; +export * from './supportedLocales'; +export * from './unwrapper'; diff --git a/packages/i18n/src/strings.ts b/packages/i18n/src/strings.ts index 7e80cbf94..679268e56 100644 --- a/packages/i18n/src/strings.ts +++ b/packages/i18n/src/strings.ts @@ -1,12 +1,12 @@ -import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/core/src/blitzkit'; import { merge } from 'lodash-es'; import type en from '../strings/en.json'; +import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from './supportedLocales'; export type TranslationTree = { [key: string]: TranslationNode }; 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 = {}; function nuke(object: TranslationTree) { @@ -21,8 +21,8 @@ function nuke(object: TranslationTree) { } SUPPORTED_LOCALES.forEach((locale) => { - const defaultStrings = files[`../../lang/${DEFAULT_LOCALE}.json`]; - const strings = files[`../../lang/${locale}.json`]; + const defaultStrings = files[`../strings/${DEFAULT_LOCALE}.json`]; + const strings = files[`../strings/${locale}.json`]; const mergedStrings = merge({}, defaultStrings, strings); /** diff --git a/packages/core/src/blitzkit/supportedLocales.ts b/packages/i18n/src/supportedLocales.ts similarity index 100% rename from packages/core/src/blitzkit/supportedLocales.ts rename to packages/i18n/src/supportedLocales.ts diff --git a/packages/i18n/src/unwrapper.ts b/packages/i18n/src/unwrapper.ts new file mode 100644 index 000000000..df57728c0 --- /dev/null +++ b/packages/i18n/src/unwrapper.ts @@ -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]; + }; +} diff --git a/packages/prate/package.json b/packages/prate/package.json deleted file mode 100644 index cab88aa90..000000000 --- a/packages/prate/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "prate", - "private": true, - "type": "module", - "main": "src/index.ts", - "devDependencies": { - "@blitzkit/core": "workspace:*" - } -} diff --git a/packages/scripts/src/applyPatches.ts b/packages/scripts/src/applyPatches.ts index a4fd58e83..878b4c27a 100644 --- a/packages/scripts/src/applyPatches.ts +++ b/packages/scripts/src/applyPatches.ts @@ -9,7 +9,7 @@ import { DATA } from './buildAssets/constants'; import { readYAMLDVPL } from './core/blitz/readYAMLDVPL'; import { writeDVPL } from './core/blitz/writeDVPL'; -const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`); +const versionTextFile = await readStringDVPL(`${DATA}/version.txt`); const currentVersion = versionTextFile .split(' ')[0] .split('.') @@ -51,7 +51,7 @@ while (true) { await mkdir(`${DATA}/${dir}`, { recursive: true }); await writeFile( - `${DATA}/${path}.dvpl`, + `${DATA}/${path}`, new Uint8Array(writeDVPL(Buffer.from(data))), ); @@ -69,13 +69,13 @@ while (true) { ); const newStrings = parseYaml(await localizationsResponse.text()); const oldStrings = await readYAMLDVPL>( - `${DATA}/Strings/en.yaml.dvpl`, + `${DATA}/Strings/en.yaml`, ); const patchedStrings = { ...oldStrings, ...newStrings }; const patchedContent = stringifyYaml(patchedStrings); await writeFile( - `${DATA}/Strings/en.yaml.dvpl`, + `${DATA}/Strings/en.yaml`, new Uint8Array(writeDVPL(Buffer.from(patchedContent))), ); } diff --git a/packages/scripts/src/buildAssets/boosterIcons.ts b/packages/scripts/src/buildAssets/boosterIcons.ts index 27710363f..d23233290 100644 --- a/packages/scripts/src/buildAssets/boosterIcons.ts +++ b/packages/scripts/src/buildAssets/boosterIcons.ts @@ -8,16 +8,16 @@ import { DATA } from './constants'; export async function boosterIcons() { 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( - await readDVPLFile(`${DATA}/Gfx/Shared/boosters/texture0.packed.webp.dvpl`), + await readDVPLFile(`${DATA}/Gfx/Shared/boosters/texture0.packed.webp`), ); const changes: FileChange[] = []; await Promise.all( boosterFiles.map(async (file) => { - const name = file.match(/booster_(.+).txt.dvpl/)?.[1]; + const name = file.match(/booster_(.+).txt/)?.[1]; if (name === undefined) return; diff --git a/packages/scripts/src/buildAssets/camouflageIcons.ts b/packages/scripts/src/buildAssets/camouflageIcons.ts index 6035876d5..88b17057a 100644 --- a/packages/scripts/src/buildAssets/camouflageIcons.ts +++ b/packages/scripts/src/buildAssets/camouflageIcons.ts @@ -6,7 +6,7 @@ import { DATA } from './constants'; export async function camouflageIcons() { console.log('Building camouflage icons...'); const content = await readDVPLFile( - `${DATA}/Gfx/UI/Hangar/IconCamouflage.packed.webp.dvpl`, + `${DATA}/Gfx/UI/Hangar/IconCamouflage.packed.webp`, ); const changes: FileChange[] = [ { diff --git a/packages/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts index 9954fe7f6..d1ab4aff3 100644 --- a/packages/scripts/src/buildAssets/constants.ts +++ b/packages/scripts/src/buildAssets/constants.ts @@ -1,11 +1,33 @@ import { readdir } from 'fs/promises'; import { argv } from 'process'; +const WINDOWS_APPS = 'C:/Program Files/WindowsApps'; +const wotbPackageRegex = /7458BE2C\..+_\d+(\.\d+)+_x64__.+/; + const isDepot = argv.includes('--depot'); -export const DATA = isDepot - ? await (async () => { - const [installationVersion] = await readdir(`../../depots/444202`); - return `../../depots/444202/${installationVersion}/Data`; - })() - : 'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data'; +let data: string; +let compressed: boolean; + +if (isDepot) { + const [installationVersion] = await readdir(`../../depots/444202`); + 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; diff --git a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts index e8e5afa3c..137bd3a30 100644 --- a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts +++ b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts @@ -34,21 +34,19 @@ export async function consumableProvisionIcons() { const styleSheets = await Promise.all( styles.map(async (path) => Object.values( - (await readYAMLDVPL(`${DATA}/${path}.dvpl`)).StyleSheets, + (await readYAMLDVPL(`${DATA}/${path}`)).StyleSheets, ), ), ).then((array) => array.flat()); for (const match of ( await readStringDVPL( - `${DATA}/XML/item_defs/vehicles/common/consumables/list.xml.dvpl`, + `${DATA}/XML/item_defs/vehicles/common/consumables/list.xml`, ) ).matchAll(listItemsPattern)) { const 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`); await Promise.all( Object.values(consumablesCommon.root).map(async (consumable) => { @@ -70,9 +68,9 @@ export async function consumableProvisionIcons() { .replace('.psd', '') .replace('.txt', ''); - if (existsSync(`${DATA}/${configPath}.packed.webp.dvpl`)) { + if (existsSync(`${DATA}/${configPath}.packed.webp`)) { const image = sharp( - await readDVPLFile(`${DATA}/${configPath}.packed.webp.dvpl`), + await readDVPLFile(`${DATA}/${configPath}.packed.webp`), ); const content = await image.trim({ threshold: 100 }).toBuffer(); @@ -83,10 +81,10 @@ export async function consumableProvisionIcons() { } else { const consumablesTexture = sharp( 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(' ') .map(Number); @@ -111,12 +109,12 @@ export async function consumableProvisionIcons() { for (const match of ( await readStringDVPL( - `${DATA}/XML/item_defs/vehicles/common/provisions/list.xml.dvpl`, + `${DATA}/XML/item_defs/vehicles/common/provisions/list.xml`, ) ).matchAll(listItemsPattern)) { const provisionsCommon = await readXMLDVPL<{ 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( Object.values(provisionsCommon.root).map(async (provision) => { const styleSheet = styleSheets.find((styleSheet) => @@ -137,9 +135,9 @@ export async function consumableProvisionIcons() { .replace('.psd', '') .replace('.txt', ''); - if (existsSync(`${DATA}/${configPath}.packed.webp.dvpl`)) { + if (existsSync(`${DATA}/${configPath}.packed.webp`)) { const image = sharp( - await readDVPLFile(`${DATA}/${configPath}.packed.webp.dvpl`), + await readDVPLFile(`${DATA}/${configPath}.packed.webp`), ); const content = await image.trim({ threshold: 100 }).toBuffer(); @@ -150,10 +148,10 @@ export async function consumableProvisionIcons() { } else { const provisionsTexture = sharp( 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(' ') .map(Number); diff --git a/packages/scripts/src/buildAssets/currencies.ts b/packages/scripts/src/buildAssets/currencies.ts index 836ad0903..ad071f090 100644 --- a/packages/scripts/src/buildAssets/currencies.ts +++ b/packages/scripts/src/buildAssets/currencies.ts @@ -23,7 +23,7 @@ export async function currencies() { await Promise.all( ICONS.map(async ([file, name]) => { const content = await sharp( - await readDVPLFile(`${DATA}/Gfx/Lobby/currency/${file}.dvpl`), + await readDVPLFile(`${DATA}/Gfx/Lobby/currency/${file}`), ) .trim() .toBuffer(); diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index 4f3b680d3..5d6ff2439 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -7,7 +7,6 @@ import { ConsumableTankCategoryFilterCategory, Crew, CrewType, - DEFAULT_LOCALE, encodePBBuffer, EquipmentDefinitions, EquipmentSlot, @@ -23,8 +22,6 @@ import { ResearchCost, ShellType, SkillDefinitions, - SUPPORTED_LOCALE_BLITZ_MAP, - SUPPORTED_LOCALES, TankClass, TankDefinitions, TankPrice, @@ -34,6 +31,11 @@ import { Unlock, Vector3, } from '@blitzkit/core'; +import { + DEFAULT_LOCALE, + SUPPORTED_LOCALE_BLITZ_MAP, + SUPPORTED_LOCALES, +} from '@blitzkit/i18n'; import { readdir } from 'fs/promises'; import { parse as parsePath } from 'path'; import { Vector3Tuple } from 'three'; @@ -513,11 +515,9 @@ export async function definitions() { console.log('Building definitions...'); const gameDefinitions: GameDefinitions = { - version: (await readStringDVPL(`${DATA}/version.txt.dvpl`)).split(' ')[0], + version: (await readStringDVPL(`${DATA}/version.txt`)).split(' ')[0], nations: ( - await readYAMLDVPL( - `${DATA}/available_nations.yaml.dvpl`, - ) + await readYAMLDVPL(`${DATA}/available_nations.yaml`) ).available_nations, gameModes: {}, roles: {}, @@ -550,26 +550,30 @@ export async function definitions() { .then((response) => response.text()) .then((string) => parseYaml(string) as BlitzStrings); 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> = {}; - for (const locale of SUPPORTED_LOCALES) { - const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; - const cache = await fetch( - `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, - ) - .then((response) => response.text()) - .then((string) => parseYaml(string) as BlitzStrings); - const preInstalled = await readYAMLDVPL( - `${DATA}/Strings/${blitzLocale}.yaml.dvpl`, - ); + await Promise.all( + SUPPORTED_LOCALES.map(async (locale) => { + const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale]; + const cache = await fetch( + `https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`, + ) + .then((response) => response.text()) + .then((string) => parseYaml(string) as BlitzStrings); + const preInstalled = await readYAMLDVPL( + `${DATA}/Strings/${blitzLocale}.yaml`, + ); - stringsI18n[locale] = { - ...cache, - ...preInstalled, - }; - } + stringsI18n[locale] = { + ...cache, + ...preInstalled, + }; + + console.log(`Loaded strings for ${locale}`); + }), + ); function getString(name: string) { const locales: Record = { @@ -594,13 +598,13 @@ export async function definitions() { const optionalDeviceSlots = await readXMLDVPL<{ 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 provisions: ProvisionsCommon = {}; for (const match of ( await readStringDVPL( - `${DATA}/XML/item_defs/vehicles/common/consumables/list.xml.dvpl`, + `${DATA}/XML/item_defs/vehicles/common/consumables/list.xml`, ) ).matchAll(//g)) { if (match[1] === 'prototypes') continue; @@ -609,7 +613,7 @@ export async function definitions() { consumables, ( 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, ); @@ -617,7 +621,7 @@ export async function definitions() { for (const match of ( await readStringDVPL( - `${DATA}/XML/item_defs/vehicles/common/provisions/list.xml.dvpl`, + `${DATA}/XML/item_defs/vehicles/common/provisions/list.xml`, ) ).matchAll(//g)) { if (match[1] === 'prototypes') continue; @@ -626,33 +630,33 @@ export async function definitions() { provisions, ( 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, ); } 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(`${DATA}/maps.yaml.dvpl`); + const maps = await readYAMLDVPL(`${DATA}/maps.yaml`); const tankXps = new Map(); 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( - `${DATA}/camouflages.yaml.dvpl`, + `${DATA}/camouflages.yaml`, ); const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages); const squadBattleTypeStyles = await readYAMLDVPL( - `${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml.dvpl`, + `${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, ); const gameTypeSelectorStyles = await readYAMLDVPL( - `${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml.dvpl`, + `${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, ); const gameModeNativeNames: Record = {}; const combatRoles = await readYAMLDVPL( - `${DATA}/XML/item_defs/vehicles/common/combat_roles.yaml.dvpl`, + `${DATA}/XML/item_defs/vehicles/common/combat_roles.yaml`, ); const consumableNativeNames: Record = {}; const provisionNativeNames: Record = {}; @@ -702,29 +706,23 @@ export async function definitions() { await Promise.all( nations.map(async (nation) => { 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<{ root: TurretDefinitionsList; - }>( - `${DATA}/XML/item_defs/vehicles/${nation}/components/turrets.xml.dvpl`, - ); + }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/turrets.xml`); const gunList = await readXMLDVPL<{ 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<{ 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<{ root: EngineDefinitionsList; - }>( - `${DATA}/XML/item_defs/vehicles/${nation}/components/engines.xml.dvpl`, - ); + }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/engines.xml`); const chassisList = await readXMLDVPL<{ root: ChassisDefinitionsList; - }>( - `${DATA}/XML/item_defs/vehicles/${nation}/components/chassis.xml.dvpl`, - ); + }>(`${DATA}/XML/item_defs/vehicles/${nation}/components/chassis.xml`); function resolveUnlocks(unlocks?: BlitzModuleType) { if (!unlocks) return []; @@ -776,10 +774,10 @@ export async function definitions() { const tank = tankList.root[tankKey]; let tankPrice: TankPrice; 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( - `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml.dvpl`, + `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, ); const turretOrigin = tankDefinition.root.hull.turretPositions.turret .split(' ') @@ -1667,8 +1665,6 @@ export async function definitions() { }; }); - return; - await commitAssets('definitions', [ { content: encodePBBuffer(GameDefinitions, gameDefinitions), diff --git a/packages/scripts/src/buildAssets/equipmentIcons.ts b/packages/scripts/src/buildAssets/equipmentIcons.ts index 762ab7b6d..e4cebc5a8 100644 --- a/packages/scripts/src/buildAssets/equipmentIcons.ts +++ b/packages/scripts/src/buildAssets/equipmentIcons.ts @@ -22,14 +22,14 @@ export async function equipmentIcons() { const changes: FileChange[] = []; 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( - `${DATA}/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml.dvpl`, + `${DATA}/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`, ); const image = sharp( 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')) { const configPathWebp = configPath.replace('.txt', ''); const image = sharp( - await readDVPLFile(`${DATA}/${configPathWebp}.packed.webp.dvpl`), + await readDVPLFile(`${DATA}/${configPathWebp}.packed.webp`), ); const content = await image.trim().toBuffer(); @@ -66,7 +66,7 @@ export async function equipmentIcons() { content, }); } else { - const config = await readStringDVPL(`${DATA}/${configPath}.dvpl`); + const config = await readStringDVPL(`${DATA}/${configPath}`); const sizes = config.split('\n')[4].split(' ').map(Number); const content = await image .clone() diff --git a/packages/scripts/src/buildAssets/flags.ts b/packages/scripts/src/buildAssets/flags.ts index 93f21b10a..2d1c8d134 100644 --- a/packages/scripts/src/buildAssets/flags.ts +++ b/packages/scripts/src/buildAssets/flags.ts @@ -14,7 +14,7 @@ export async function flags() { .filter( (flag) => flag.startsWith('flag_profile-stat_') && - !flag.endsWith('@2x.packed.webp.dvpl'), + !flag.endsWith('@2x.packed.webp'), ) .map(async (flag) => { const image = sharp( @@ -34,7 +34,7 @@ export async function flags() { .filter( (flag) => flag.startsWith('flag_tutor-tank_') && - !flag.endsWith('@2x.packed.webp.dvpl'), + !flag.endsWith('@2x.packed.webp'), ) .map(async (flag) => { const content = await readDVPLFile(`${DATA}/Gfx/Lobby/flags/${flag}`); @@ -50,8 +50,7 @@ export async function flags() { (await readdir(`${DATA}/Gfx/Lobby/flags`)) .filter( (flag) => - flag.startsWith('flag_filter_') && - flag.endsWith('@2x.packed.webp.dvpl'), + flag.startsWith('flag_filter_') && flag.endsWith('@2x.packed.webp'), ) .map(async (flag) => { const content = await sharp( diff --git a/packages/scripts/src/buildAssets/gameModeBanners.ts b/packages/scripts/src/buildAssets/gameModeBanners.ts index c30e6b223..7923e6c60 100644 --- a/packages/scripts/src/buildAssets/gameModeBanners.ts +++ b/packages/scripts/src/buildAssets/gameModeBanners.ts @@ -11,10 +11,10 @@ export async function gameModeBanners() { const changes: FileChange[] = []; const gameTypeSelectorStyles = await readYAMLDVPL( - `${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml.dvpl`, + `${DATA}/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`, ); const squadBattleTypeStyles = await readYAMLDVPL( - `${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml.dvpl`, + `${DATA}/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`, ); const bannerMatches: { name: string; path: string }[] = []; @@ -38,7 +38,7 @@ export async function gameModeBanners() { } 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 } }) .toBuffer(); diff --git a/packages/scripts/src/buildAssets/moduleIcons.ts b/packages/scripts/src/buildAssets/moduleIcons.ts index 23d240f67..b446a7314 100644 --- a/packages/scripts/src/buildAssets/moduleIcons.ts +++ b/packages/scripts/src/buildAssets/moduleIcons.ts @@ -12,10 +12,10 @@ export async function moduleIcons() { (await readdir(`${DATA}/Gfx/UI/ModulesTechTree`)) .filter( (file) => - !file.endsWith('@2x.packed.webp.dvpl') && file.startsWith('vehicle'), + !file.endsWith('@2x.packed.webp') && file.startsWith('vehicle'), ) .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 content = await sharp( await readDVPLFile(`${DATA}/Gfx/UI/ModulesTechTree/${file}`), diff --git a/packages/scripts/src/buildAssets/shellIcons.ts b/packages/scripts/src/buildAssets/shellIcons.ts index c20f5c12c..0e8d1f81b 100644 --- a/packages/scripts/src/buildAssets/shellIcons.ts +++ b/packages/scripts/src/buildAssets/shellIcons.ts @@ -11,15 +11,15 @@ export async function shellIcons() { const image = sharp( 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( (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) => { - const name = file.match(/(.+)_l\.txt\.dvpl/)![1]; + const name = file.match(/(.+)_l\.txt/)![1]; const sizes = ( await readStringDVPL( `${DATA}/Gfx/Shared/tank-supply/ammunition/big/${file}`, diff --git a/packages/scripts/src/buildAssets/skillIcons.ts b/packages/scripts/src/buildAssets/skillIcons.ts index 98bb67f5b..191b90969 100644 --- a/packages/scripts/src/buildAssets/skillIcons.ts +++ b/packages/scripts/src/buildAssets/skillIcons.ts @@ -29,13 +29,13 @@ export async function skillIcons() { console.log('Building skill icons...'); 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( Object.values(avatar.root.skills).map(async (skill) => { const icon = Array.isArray(skill.icon) ? skill.icon[0] : skill.icon; 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 content = await image.toBuffer(); diff --git a/packages/scripts/src/buildAssets/tankArmors.ts b/packages/scripts/src/buildAssets/tankArmors.ts index 96b956a8d..fd733144e 100644 --- a/packages/scripts/src/buildAssets/tankArmors.ts +++ b/packages/scripts/src/buildAssets/tankArmors.ts @@ -20,7 +20,7 @@ export async function tankArmors() { await Promise.all( nations.map(async (nation) => { 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( diff --git a/packages/scripts/src/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts index d3421c42d..42d4ba309 100644 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ b/packages/scripts/src/buildAssets/tankIcons.ts @@ -53,7 +53,7 @@ export async function tankIcons() { await Promise.all( nations.map(async (nation) => { 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( @@ -63,14 +63,14 @@ export async function tankIcons() { const nationVehicleId = tank.id; const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; const parameters = await readYAMLDVPL( - `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml.dvpl`, + `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, ); const smallPath = `${DATA}/${parameters.resourcesPath.smallIconPath .replace(/~res:\//, '') - .replace(/\..+/, '')}.packed.webp.dvpl`; + .replace(/\..+/, '')}.packed.webp`; const bigPath = `${DATA}/${parameters.resourcesPath.bigIconPath .replace(/~res:\//, '') - .replace(/\..+/, '')}.packed.webp.dvpl`; + .replace(/\..+/, '')}.packed.webp`; const big = await sharp(await readDVPLFile(bigPath)) .trim() .toBuffer(); diff --git a/packages/scripts/src/buildAssets/tankModels.ts b/packages/scripts/src/buildAssets/tankModels.ts index 9a2a759ad..112889086 100644 --- a/packages/scripts/src/buildAssets/tankModels.ts +++ b/packages/scripts/src/buildAssets/tankModels.ts @@ -23,7 +23,7 @@ export async function tankModels() { const nation = nations[nationIndex]; const changes: FileChange[] = []; 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}`); @@ -35,7 +35,7 @@ export async function tankModels() { const id = toUniqueId(nation, tank.id); const parameters = await readYAMLDVPL( - `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml.dvpl`, + `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, ); const model = await extractModel( DATA, diff --git a/packages/scripts/src/core/blitz/extractArmor.ts b/packages/scripts/src/core/blitz/extractArmor.ts index a5c6e6b2d..5d55e9a63 100644 --- a/packages/scripts/src/core/blitz/extractArmor.ts +++ b/packages/scripts/src/core/blitz/extractArmor.ts @@ -13,8 +13,8 @@ import { import { readDVPLFile } from './readDVPLFile'; export async function extractArmor(data: string, fileName: string) { - const sc2Path = `${data}/3d/Tanks/CollisionMeshes/${fileName}.sc2.dvpl`; - const scgPath = `${data}/3d/Tanks/CollisionMeshes/${fileName}.scg.dvpl`; + const sc2Path = `${data}/3d/Tanks/CollisionMeshes/${fileName}.sc2`; + const scgPath = `${data}/3d/Tanks/CollisionMeshes/${fileName}.scg`; const sc2 = new Sc2ReadStream( (await readDVPLFile(sc2Path)).buffer as ArrayBuffer, ).sc2(); diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts index a1a036b12..e53941803 100644 --- a/packages/scripts/src/core/blitz/extractModel/index.ts +++ b/packages/scripts/src/core/blitz/extractModel/index.ts @@ -26,8 +26,8 @@ const omitMeshNames = { }; export async function extractModel(data: string, path: string) { - const sc2Path = `${data}/3d/${path}.sc2.dvpl`; - const scgPath = `${data}/3d/${path}.scg.dvpl`; + const sc2Path = `${data}/3d/${path}.sc2`; + const scgPath = `${data}/3d/${path}.scg`; const sc2 = new Sc2ReadStream( (await readDVPLFile(sc2Path)).buffer as ArrayBuffer, ).sc2(); diff --git a/packages/scripts/src/core/blitz/readDVPLFile.ts b/packages/scripts/src/core/blitz/readDVPLFile.ts index ef83c77b2..bd0f0d091 100644 --- a/packages/scripts/src/core/blitz/readDVPLFile.ts +++ b/packages/scripts/src/core/blitz/readDVPLFile.ts @@ -1,6 +1,11 @@ import { readFile } from 'fs/promises'; +import { DATA_COMPRESSED } from '../../buildAssets/constants'; import { readDVPL } from './readDVPL'; export async function readDVPLFile(file: string) { - return readDVPL(await readFile(file)); + if (DATA_COMPRESSED) { + return readDVPL(await readFile(`${file}.dvpl`)); + } + + return readFile(file); } diff --git a/packages/scripts/src/core/blitz/readTexture/index.ts b/packages/scripts/src/core/blitz/readTexture/index.ts index ce3ff9e88..094bc8803 100644 --- a/packages/scripts/src/core/blitz/readTexture/index.ts +++ b/packages/scripts/src/core/blitz/readTexture/index.ts @@ -5,7 +5,7 @@ import { readDVPLFile } from '../readDVPLFile'; import { TextureMutation } from './constants'; 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 resolvedTexturePath = isDds ? ddsTexturePath diff --git a/packages/website/astro.config.mjs b/packages/website/astro.config.mjs index 4eb634a07..3b002a848 100644 --- a/packages/website/astro.config.mjs +++ b/packages/website/astro.config.mjs @@ -2,13 +2,13 @@ import react from '@astrojs/react'; import sitemap from '@astrojs/sitemap'; -import { SUPPORTED_LOCALES } from '@blitzkit/core'; +import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; import { defineConfig } from 'astro/config'; export default defineConfig({ i18n: { defaultLocale: 'en', - locales: SUPPORTED_LOCALES, + locales: [...SUPPORTED_LOCALES], routing: { prefixDefaultLocale: false, }, diff --git a/packages/website/src/components/Compare/Body.tsx b/packages/website/src/components/Compare/Body.tsx index c87b2e532..3788536cb 100644 --- a/packages/website/src/components/Compare/Body.tsx +++ b/packages/website/src/components/Compare/Body.tsx @@ -1,6 +1,7 @@ import { asset } from '@blitzkit/core'; import { Flex, Heading, IconButton, Table } from '@radix-ui/themes'; import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics'; +import { useLocale } from '../../hooks/useLocale'; import { CompareEphemeral } from '../../stores/compareEphemeral'; import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell'; import { CompareRow } from './CompareRow'; @@ -15,6 +16,7 @@ export function Body({ stats }: BodyProps) { const hasNonRegularGun = members.some( ({ gun }) => gun.gun_type!.$case !== 'regular', ); + const { unwrap } = useLocale(); const mutateCompareEphemeral = CompareEphemeral.useMutation(); return ( @@ -60,7 +62,7 @@ export function Body({ stats }: BodyProps) { }} > {thisShell.name} state.crewSkills); const members = CompareEphemeral.use((state) => state.members); const mutateCompareEphemeral = CompareEphemeral.useMutation(); + const { unwrap } = useLocale(); return ( {provisions.map((provision, index) => ( { {consumables.map((consumable, index) => ( {(null); const mutateCompareEphemeral = CompareEphemeral.useMutation(); + const { unwrap } = useLocale(); useEffect(() => { const initial = new Vector2(); @@ -118,7 +120,7 @@ export function TankCard({ index, tank }: TankCardProps) { }} > {tank.name} - {tank.name} + {unwrap(tank.name)} diff --git a/packages/website/src/components/Embeds/GenerateURL.tsx b/packages/website/src/components/Embeds/GenerateURL.tsx index 9d1d31c8f..57a8f4ef0 100644 --- a/packages/website/src/components/Embeds/GenerateURL.tsx +++ b/packages/website/src/components/Embeds/GenerateURL.tsx @@ -3,7 +3,7 @@ import { type AccountListItem, type AccountListWithServer, } 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 { Badge, diff --git a/packages/website/src/components/Embeds/breakdown.tsx b/packages/website/src/components/Embeds/breakdown.tsx index 279a3dad4..5c2524f0e 100644 --- a/packages/website/src/components/Embeds/breakdown.tsx +++ b/packages/website/src/components/Embeds/breakdown.tsx @@ -8,7 +8,7 @@ import { sumCompositeStats, type IndividualTankStats, } 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 { ContextMenu } from '@radix-ui/themes'; import { useEffect, useMemo, useState } from 'react'; diff --git a/packages/website/src/components/Home/HomeLocaleSwitcher.tsx b/packages/website/src/components/Home/HomeLocaleSwitcher.tsx index 0c5301ef4..908e8a941 100644 --- a/packages/website/src/components/Home/HomeLocaleSwitcher.tsx +++ b/packages/website/src/components/Home/HomeLocaleSwitcher.tsx @@ -1,4 +1,4 @@ -import { DEFAULT_LOCALE } from '@blitzkit/core'; +import { DEFAULT_LOCALE } from '@blitzkit/i18n'; import { GlobeIcon } from '@radix-ui/react-icons'; import { Flex, Text } from '@radix-ui/themes'; import type { LocaleAcceptorProps } from '../../hooks/useLocale'; diff --git a/packages/website/src/components/LinkI18n.tsx b/packages/website/src/components/LinkI18n.tsx index 18f8f6a2e..1a5c1936a 100644 --- a/packages/website/src/components/LinkI18n.tsx +++ b/packages/website/src/components/LinkI18n.tsx @@ -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 type { LocaleAcceptorProps } from '../hooks/useLocale'; diff --git a/packages/website/src/components/LocaleSwitcher.tsx b/packages/website/src/components/LocaleSwitcher.tsx index 42e30ada5..4ba507a08 100644 --- a/packages/website/src/components/LocaleSwitcher.tsx +++ b/packages/website/src/components/LocaleSwitcher.tsx @@ -1,9 +1,10 @@ +import {} from '@blitzkit/core'; import { DEFAULT_LOCALE, - localizedStrings, - SUPPORTED_LOCALE_FLAGS, SUPPORTED_LOCALES, -} from '@blitzkit/core'; + SUPPORTED_LOCALE_FLAGS, + localizedStrings, +} from '@blitzkit/i18n'; import { Select } from '@radix-ui/themes'; import type { LocaleAcceptorProps } from '../hooks/useLocale'; diff --git a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx index 9ed3f42ed..278505e33 100644 --- a/packages/website/src/components/ModuleButtons/ConsumableButton.tsx +++ b/packages/website/src/components/ModuleButtons/ConsumableButton.tsx @@ -3,6 +3,7 @@ import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons'; import { Flex, Text } from '@radix-ui/themes'; import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions'; import { useDelta } from '../../hooks/useDelta'; +import { useLocale } from '../../hooks/useLocale'; import { GenericTankComponentButton } from './GenericTankComponentButton'; import type { TankComponentButtonProps } from './TankComponentButton'; @@ -22,11 +23,12 @@ export function ConsumableButton({ }: ConsumableButtonProps) { const cooldownDelta = useDelta(cooldown ?? 0); const durationDelta = useDelta(duration ?? 0); + const { unwrap } = useLocale(); return ( diff --git a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx index f155f5e3c..93014bd16 100644 --- a/packages/website/src/components/ModuleButtons/EquipmentButton.tsx +++ b/packages/website/src/components/ModuleButtons/EquipmentButton.tsx @@ -1,5 +1,6 @@ import { asset } from '@blitzkit/core'; import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions'; +import { useLocale } from '../../hooks/useLocale'; import { GenericTankComponentButton } from './GenericTankComponentButton'; import type { TankComponentButtonProps } from './TankComponentButton'; @@ -10,9 +11,11 @@ interface EquipmentButtonProps extends TankComponentButtonProps { const equipmentDefinitions = await awaitableEquipmentDefinitions; export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) { + const { unwrap } = useLocale(); + return ( diff --git a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx index dc4de0837..c42c0493b 100644 --- a/packages/website/src/components/ModuleButtons/ProvisionButton.tsx +++ b/packages/website/src/components/ModuleButtons/ProvisionButton.tsx @@ -1,5 +1,6 @@ import { asset } from '@blitzkit/core'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; +import { useLocale } from '../../hooks/useLocale'; import { GenericTankComponentButton } from './GenericTankComponentButton'; import type { TankComponentButtonProps } from './TankComponentButton'; @@ -10,9 +11,11 @@ interface ProvisionButtonProps extends TankComponentButtonProps { const provisionDefinitions = await awaitableProvisionDefinitions; export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) { + const { unwrap } = useLocale(); + return ( diff --git a/packages/website/src/components/ModuleManager.tsx b/packages/website/src/components/ModuleManager.tsx index 01784ba37..90b496968 100644 --- a/packages/website/src/components/ModuleManager.tsx +++ b/packages/website/src/components/ModuleManager.tsx @@ -9,6 +9,7 @@ import { } from '@blitzkit/core'; import { DropdownMenu, Flex } from '@radix-ui/themes'; import { useState } from 'react'; +import { useLocale } from '../hooks/useLocale'; import { ModuleButton } from './ModuleButtons/ModuleButton'; interface ModuleManagerProps { @@ -35,6 +36,7 @@ export function ModuleManager({ const [gunMenuOpen, setGunMenuOpen] = useState(false); const [engineMenuOpen, setEngineMenuOpen] = useState(false); const [trackMenuOpen, setTrackMenuOpen] = useState(false); + const { unwrap } = useLocale(); return ( @@ -71,7 +73,7 @@ export function ModuleManager({ module="turret" discriminator={TIER_ROMAN_NUMERALS[turret.tier]} />{' '} - {turret.name} + {unwrap(turret.name)} ); })} @@ -117,7 +119,7 @@ export function ModuleManager({ TIER_ROMAN_NUMERALS[gun.gun_type!.value.base.tier] } />{' '} - {gun.gun_type!.value.base.name} + {unwrap(gun.gun_type!.value.base.name)} ); })} @@ -156,7 +158,7 @@ export function ModuleManager({ module="engine" discriminator={TIER_ROMAN_NUMERALS[engine.tier]} />{' '} - {engine.name} + {unwrap(engine.name)} ); })} @@ -195,7 +197,7 @@ export function ModuleManager({ module="chassis" discriminator={TIER_ROMAN_NUMERALS[track.tier]} />{' '} - {track.name} + {unwrap(track.name)} ); })} diff --git a/packages/website/src/components/TankCard/index.tsx b/packages/website/src/components/TankCard/index.tsx index 85647bbc4..52860d4bd 100644 --- a/packages/website/src/components/TankCard/index.tsx +++ b/packages/website/src/components/TankCard/index.tsx @@ -28,7 +28,7 @@ export const TankCard = forwardRef( }: TankCardProps, ref, ) => { - const { locale } = useLocale(); + const { unwrap, locale } = useLocale(); const provideLink = !noLink && onSelect === undefined; const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const Icon = classIcons[tank.class]; @@ -73,7 +73,7 @@ export const TankCard = forwardRef( }} > {tank.name}( > - {tank.name} + {unwrap(tank.name)} diff --git a/packages/website/src/components/TankRowHeaderCell.tsx b/packages/website/src/components/TankRowHeaderCell.tsx index 788ae6ef5..c2431ae49 100644 --- a/packages/website/src/components/TankRowHeaderCell.tsx +++ b/packages/website/src/components/TankRowHeaderCell.tsx @@ -10,7 +10,7 @@ interface TankRowHeaderCellProps { } export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) { - const { locale } = useLocale(); + const { locale, unwrap } = useLocale(); const Icon = classIcons[tank.class]; return ( @@ -24,7 +24,7 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) { tabIndex={-1} > {tank.name} )} - {tank.name} + {unwrap(tank.name)} ); diff --git a/packages/website/src/components/TankSearch/components/QuickLink.tsx b/packages/website/src/components/TankSearch/components/QuickLink.tsx index 4a6dc8379..8dd779024 100644 --- a/packages/website/src/components/TankSearch/components/QuickLink.tsx +++ b/packages/website/src/components/TankSearch/components/QuickLink.tsx @@ -12,7 +12,7 @@ interface QuickLinkProps { export function QuickLink({ topResult }: QuickLinkProps) { const tankFilters = useStore($tankFilters); - const { locale } = useLocale(); + const { locale, unwrap } = useLocale(); if (!tankFilters.search || !topResult || tankFilters.searching) return null; @@ -20,7 +20,7 @@ export function QuickLink({ topResult }: QuickLinkProps) { diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx index 7f67c23da..da3ddc9ca 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx @@ -27,6 +27,7 @@ import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/pr import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics'; import { useEquipment } from '../../../../../hooks/useEquipment'; +import { useLocale } from '../../../../../hooks/useLocale'; import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition'; import { Duel } from '../../../../../stores/duel'; import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral'; @@ -39,6 +40,7 @@ const [equipmentDefinitions, provisionDefinitions] = await Promise.all([ ]); export function Characteristics() { + const { unwrap } = useLocale(); const mutateDuel = Duel.useMutation(); const crewSkills = TankopediaEphemeral.use((state) => state.skills); const penetrationDistanceInput = useRef(null); @@ -146,7 +148,7 @@ export function Characteristics() { {gun.gun_type!.value.base.shells.map((thisShell, shellIndex) => ( - + {thisShell.name} - {formatCompact(unlock.cost.value)} + {formatCompact(locale, unlock.cost.value)} state.protagonist.tank); const roles = Object.entries(tank.roles); @@ -67,7 +69,7 @@ export function GameModeSection() { justify="center" > - {gameMode.name} + {unwrap(gameMode.name)} @@ -84,9 +86,9 @@ export function GameModeSection() { objectFit: 'contain', }} src={asset(`icons/provisions/${id}.webp`)} - alt={provisions.name} + alt={unwrap(provisions.name)} /> - {provisions.name} + {unwrap(provisions.name)} ); @@ -104,9 +106,9 @@ export function GameModeSection() { objectFit: 'contain', }} src={asset(`icons/consumables/${id}.webp`)} - alt={consumable.name} + alt={unwrap(consumable.name)} /> - {consumable.name} + {unwrap(consumable.name)} ); diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index 26f8a0119..cf5f90fe8 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -89,7 +89,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); - const { strings } = useLocale(); + const { strings, unwrap } = useLocale(); return ( <> @@ -244,7 +244,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { }} > {thisShell.name} - {antagonistTank.name} + {unwrap(antagonistTank.name)} diff --git a/packages/website/src/components/Tankopedia/HeroSection/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/index.tsx index d78b279ae..2b653382d 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/index.tsx @@ -6,6 +6,7 @@ import { NAVBAR_HEIGHT } from '../../../constants/navbar'; import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; import { Var } from '../../../core/radix/var'; import { useFullScreen } from '../../../hooks/useFullScreen'; +import { useLocale } from '../../../hooks/useLocale'; import { Duel } from '../../../stores/duel'; import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral'; import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps'; @@ -18,6 +19,7 @@ import { TankSandboxLoader } from './components/TankSandboxLoader'; const tankDefinitions = await awaitableTankDefinitions; export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { + const { unwrap } = useLocale(); const canvas = useRef(null); const isFullScreen = useFullScreen(); const protagonist = Duel.use((state) => state.protagonist.tank); @@ -117,7 +119,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { wrap="nowrap" color={treeColor} > - {protagonist.name} + {unwrap(protagonist.name)} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx index 09941088b..3df77e1e0 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/components/VoteCaster.tsx @@ -9,6 +9,7 @@ import { Text, } from '@radix-ui/themes'; import { useState } from 'react'; +import { useLocale } from '../../../../../../hooks/useLocale'; import { App } from '../../../../../../stores/app'; import { Duel } from '../../../../../../stores/duel'; import { Stars } from '../../Stars'; @@ -24,6 +25,7 @@ export function VoteCaster() { const [postVote, setPostVote] = useState(null); // true = open, false = closed, null = casting const [showDialogue, setShowDialogue] = useState(false); + const { unwrap } = useLocale(); return ( <> @@ -35,7 +37,7 @@ export function VoteCaster() { - Voting for {tank.name} + Voting for {unwrap(tank.name)} diff --git a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx index 70d75aec8..5455a4327 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/components/Votes/index.tsx @@ -1,6 +1,7 @@ import { formatCompact } from '@blitzkit/core'; import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes'; import { memo } from 'react'; +import { useLocale } from '../../../../../hooks/useLocale'; import { useTankVotes } from '../../../../../hooks/useTankVotes'; import { App } from '../../../../../stores/app'; import { Duel } from '../../../../../stores/duel'; @@ -13,13 +14,14 @@ export const Votes = memo( const id = Duel.use((state) => state.protagonist.tank.id); const votes = useTankVotes(id); const wargaming = App.use((state) => state.logins.wargaming); + const { locale } = useLocale(); return ( {!votes && } - {votes && formatCompact(votes.votes)}{' '} + {votes && formatCompact(locale, votes.votes)}{' '} {votes && (votes.votes > 1 ? 'votes' : 'vote')} diff --git a/packages/website/src/components/Tankopedia/MetaSection/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/index.tsx index b95ae93fa..312b98289 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/index.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/index.tsx @@ -38,7 +38,7 @@ export function MetaSection() { const mutateDuel = Duel.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const [showSwapDialog, setShowSwapDialog] = useState(false); - const { locale, strings } = useLocale(); + const { locale, strings, unwrap } = useLocale(); return ( @@ -124,7 +124,7 @@ export function MetaSection() { {protagonist.name_full && ( - {protagonist.name_full} + {unwrap(protagonist.name_full)} )} diff --git a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx index 76e7bb8bb..aa29504a1 100644 --- a/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx +++ b/packages/website/src/components/Tankopedia/TechTreeSection/components/Node.tsx @@ -20,7 +20,7 @@ const [tankDefinitions, averageDefinitions] = await Promise.all([ ]); 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 tank = tankDefinitions.tanks[id]; const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]); @@ -78,7 +78,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) { }} > {tank.name} {TIER_ROMAN_NUMERALS[tank.tier]} - {tank.name} + {unwrap(tank.name)} diff --git a/packages/website/src/components/TanksEmbed.tsx b/packages/website/src/components/TanksEmbed.tsx index 3d969047c..6172801d7 100644 --- a/packages/website/src/components/TanksEmbed.tsx +++ b/packages/website/src/components/TanksEmbed.tsx @@ -6,13 +6,14 @@ import { type CompositeStats, type CompositeStatsKey, } 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 { Flex, Text, type FlexProps } from '@radix-ui/themes'; import { times } from 'lodash-es'; import type { ReactNode } from 'react'; import { breakdownConfig } from '../constants/embeds'; import { toRadiusVar } from '../core/radix/utils'; +import { useLocale } from '../hooks/useLocale'; import { useEmbedStateCurry } from '../stores/embedState/utilities'; import { ClassHeavy } from './ClassIcon/components/ClassHeavy'; import { ClassLight } from './ClassIcon/components/ClassLight'; @@ -52,6 +53,7 @@ export function BreakdownEmbedCard({ }: BreakdownEmbedCardProps) { const { useEmbedState, useRichText } = useEmbedStateCurry(); + const { unwrap } = useLocale(); return ( diff --git a/packages/website/src/core/blitzkit/branches.ts b/packages/website/src/core/blitzkit/branches.ts index e550bd1a9..2f3f6933b 100644 --- a/packages/website/src/core/blitzkit/branches.ts +++ b/packages/website/src/core/blitzkit/branches.ts @@ -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) { const strings = localizedStrings[locale]; diff --git a/packages/website/src/core/i18n/getBestLocale.ts b/packages/website/src/core/i18n/getBestLocale.ts index 0aa502545..543a0ee29 100644 --- a/packages/website/src/core/i18n/getBestLocale.ts +++ b/packages/website/src/core/i18n/getBestLocale.ts @@ -1,4 +1,4 @@ -import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/core'; +import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/i18n'; export function getBestLocale() { const desiredLocale = diff --git a/packages/website/src/core/i18n/getStrings.ts b/packages/website/src/core/i18n/getStrings.ts index fd9422fe5..a83acbe33 100644 --- a/packages/website/src/core/i18n/getStrings.ts +++ b/packages/website/src/core/i18n/getStrings.ts @@ -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) { return localizedStrings[locale]; diff --git a/packages/website/src/hooks/useLocale.tsx b/packages/website/src/hooks/useLocale.tsx index 186dc61c4..b58bdc5fb 100644 --- a/packages/website/src/hooks/useLocale.tsx +++ b/packages/website/src/hooks/useLocale.tsx @@ -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 { getStrings } from '../core/i18n/getStrings'; @@ -29,8 +29,9 @@ export function useLocale() { } const strings = getStrings(context.locale); + const unwrap = unwrapper(context.locale); - return { locale: context.locale, strings }; + return { locale: context.locale, strings, unwrap }; } export interface LocaleAcceptorProps { diff --git a/packages/website/src/layouts/BaseLayout.astro b/packages/website/src/layouts/BaseLayout.astro index a6344f7cc..ce10f8f0f 100644 --- a/packages/website/src/layouts/BaseLayout.astro +++ b/packages/website/src/layouts/BaseLayout.astro @@ -6,7 +6,7 @@ import Footer from '../components/Footer.astro'; import Navbar from '../components/Navbar.astro'; import Party3 from '../components/Party3.astro'; import './base.css'; -import { DEFAULT_LOCALE } from '@blitzkit/core'; +import { DEFAULT_LOCALE } from '@blitzkit/i18n'; export interface Props { noFooter?: boolean; @@ -40,7 +40,7 @@ const isRoot = pathname === '/';