From d8459d373c104d5ad05d8fcfb74eb45edac97491 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:38:41 -0600 Subject: [PATCH] feat: start of localized proto strings --- packages/core/src/i18n/index.ts | 2 - packages/core/src/i18n/translator.ts | 90 -------- packages/core/src/index.ts | 2 - .../core/src/protos/average_definitions.ts | 2 +- .../src/protos/camouflage_definitions.proto | 8 +- .../core/src/protos/camouflage_definitions.ts | 59 ++--- .../src/protos/consumable_definitions.proto | 4 +- .../core/src/protos/consumable_definitions.ts | 31 ++- .../src/protos/equipment_definitions.proto | 6 +- .../core/src/protos/equipment_definitions.ts | 43 ++-- .../core/src/protos/game_definitions.proto | 4 +- packages/core/src/protos/game_definitions.ts | 25 ++- packages/core/src/protos/i18n.proto | 7 + packages/core/src/protos/i18n.ts | 212 ++++++++++++++++++ packages/core/src/protos/index.ts | 1 + .../core/src/protos/map_definitions.proto | 4 +- packages/core/src/protos/map_definitions.ts | 23 +- packages/core/src/protos/model_definitions.ts | 8 +- .../src/protos/provision_definitions.proto | 3 +- .../core/src/protos/provision_definitions.ts | 23 +- packages/core/src/protos/reviews.ts | 2 +- packages/core/src/protos/skill_definitions.ts | 4 +- .../core/src/protos/tank_definitions.proto | 17 +- packages/core/src/protos/tank_definitions.ts | 168 +++++++------- packages/core/tsconfig.json | 2 +- packages/i18n/package.json | 12 + packages/i18n/src/index.ts | 1 + .../{core/src/i18n => i18n/src}/strings.ts | 9 +- packages/{core/lang => i18n/strings}/en.json | 0 packages/{core/lang => i18n/strings}/es.json | 0 packages/{core/lang => i18n/strings}/fr.json | 0 packages/{core/lang => i18n/strings}/ja.json | 0 packages/{core/lang => i18n/strings}/pt.json | 0 packages/{core/lang => i18n/strings}/ru.json | 0 packages/{core/lang => i18n/strings}/uk.json | 0 packages/{core/lang => i18n/strings}/zh.json | 0 packages/i18n/vite-env.d.ts | 1 + packages/scripts/package.json | 1 + .../scripts/src/buildAssets/definitions.ts | 117 +++++----- packages/website/package.json | 2 +- .../components/Tankopedia/HistorySection.tsx | 16 -- .../tools/tankopedia/[id]/_index.tsx | 1 - 42 files changed, 550 insertions(+), 360 deletions(-) delete mode 100644 packages/core/src/i18n/index.ts delete mode 100644 packages/core/src/i18n/translator.ts create mode 100644 packages/core/src/protos/i18n.proto create mode 100644 packages/core/src/protos/i18n.ts create mode 100644 packages/i18n/package.json create mode 100644 packages/i18n/src/index.ts rename packages/{core/src/i18n => i18n/src}/strings.ts (85%) rename packages/{core/lang => i18n/strings}/en.json (100%) rename packages/{core/lang => i18n/strings}/es.json (100%) rename packages/{core/lang => i18n/strings}/fr.json (100%) rename packages/{core/lang => i18n/strings}/ja.json (100%) rename packages/{core/lang => i18n/strings}/pt.json (100%) rename packages/{core/lang => i18n/strings}/ru.json (100%) rename packages/{core/lang => i18n/strings}/uk.json (100%) rename packages/{core/lang => i18n/strings}/zh.json (100%) create mode 100644 packages/i18n/vite-env.d.ts delete mode 100644 packages/website/src/components/Tankopedia/HistorySection.tsx diff --git a/packages/core/src/i18n/index.ts b/packages/core/src/i18n/index.ts deleted file mode 100644 index d74dfb75c..000000000 --- a/packages/core/src/i18n/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './strings'; -export * from './translator'; diff --git a/packages/core/src/i18n/translator.ts b/packages/core/src/i18n/translator.ts deleted file mode 100644 index 742431c49..000000000 --- a/packages/core/src/i18n/translator.ts +++ /dev/null @@ -1,90 +0,0 @@ -export type TranslationTree = { [key: string]: TranslationNode }; - -export type TranslationNode = string | TranslationTree; - -type NestedKeys = Type extends object - ? { - [Key in keyof Type]: Key extends string - ? Type[Key] extends object - ? '$' extends keyof Type[Key] - ? `${Key}` | `${Key}.${NestedKeys}` - : `${Key}.${NestedKeys}` - : Key - : never; - }[keyof Type] - : never; - -export class Translator< - Locale extends string, - DefaultLocale extends Locale, - Strings extends TranslationTree, - Path extends NestedKeys, -> { - private readonly defaultStrings: Map; - - constructor( - private strings: Record, - private defaultLocale: DefaultLocale, - ) { - this.defaultStrings = this.flatten(strings[defaultLocale]); - } - - private flatten(strings: TranslationTree) { - const translations = new Map(); - - function traverse(parent: string, tree: Record) { - for (const key in tree) { - const path = `${parent}${key}`; - - if (typeof tree[key] === 'string') { - translations.set(path, tree[key]); - } else { - traverse(`${path}.`, tree[key]); - } - } - } - - traverse('', strings); - - return translations; - } - - locale(locale: Locale) { - if (this.strings[locale] === undefined) { - throw new TypeError(`Locale "${locale}" is not defined`); - } - - const strings = this.strings[locale]; - const defaultStrings = this.defaultStrings; - const defaultLocale = this.defaultLocale; - - const translations = this.flatten(strings); - - function translate(path: Path, literals?: string[]) { - let translation = translations.get(path) ?? translations.get(`${path}.$`); - - if (translation === undefined) { - translation = - defaultStrings.get(path) ?? defaultStrings.get(`${path}.$`); - - if (translation === undefined) { - throw new Error( - `Undefined translation for "${path}" in locale "${locale}"; no fallback found in "${defaultLocale}"`, - ); - } - - console.warn( - `Undefined translation for "${path}" in locale "${locale}"; falling back to "${defaultLocale}"`, - ); - } - - literals?.forEach((literal, index) => { - translation = translation!.replaceAll(`%s${index + 1}`, literal); - }); - - return translation; - } - - return { translate, t: translate }; - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6dc0f8de4..653e639b5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,8 +1,6 @@ export * from './blitz'; export * from './blitzkit'; export * from './databases'; -export * from './i18n'; -export * from './imgur'; export * from './math'; export * from './patreon'; export * from './protobuf'; diff --git a/packages/core/src/protos/average_definitions.ts b/packages/core/src/protos/average_definitions.ts index 806ce7d72..507678f6d 100644 --- a/packages/core/src/protos/average_definitions.ts +++ b/packages/core/src/protos/average_definitions.ts @@ -251,7 +251,7 @@ export const AverageDefinitions_AveragesEntry: MessageFns camouflages = 1; } message Camouflage { required uint32 id = 1; - required string name = 2; - optional string tank_name = 3; - optional string tank_name_full = 4; + required I18nString name = 2; + optional I18nString tank_name = 3; + optional I18nString tank_name_full = 4; } \ No newline at end of file diff --git a/packages/core/src/protos/camouflage_definitions.ts b/packages/core/src/protos/camouflage_definitions.ts index 2dc973d98..659f1aeb0 100644 --- a/packages/core/src/protos/camouflage_definitions.ts +++ b/packages/core/src/protos/camouflage_definitions.ts @@ -6,6 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -20,9 +21,9 @@ export interface CamouflageDefinitions_CamouflagesEntry { export interface Camouflage { id: number; - name: string; - tank_name?: string | undefined; - tank_name_full?: string | undefined; + name: I18nString; + tank_name?: I18nString | undefined; + tank_name_full?: I18nString | undefined; } function createBaseCamouflageDefinitions(): CamouflageDefinitions { @@ -184,13 +185,13 @@ export const CamouflageDefinitions_CamouflagesEntry: MessageFns = { @@ -198,14 +199,14 @@ export const Camouflage: MessageFns = { if (message.id !== 0) { writer.uint32(8).uint32(message.id); } - if (message.name !== "") { - writer.uint32(18).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(18).fork()).join(); } - if (message.tank_name !== undefined && message.tank_name !== undefined) { - writer.uint32(26).string(message.tank_name); + if (message.tank_name !== undefined) { + I18nString.encode(message.tank_name, writer.uint32(26).fork()).join(); } - if (message.tank_name_full !== undefined && message.tank_name_full !== undefined) { - writer.uint32(34).string(message.tank_name_full); + if (message.tank_name_full !== undefined) { + I18nString.encode(message.tank_name_full, writer.uint32(34).fork()).join(); } return writer; }, @@ -230,7 +231,7 @@ export const Camouflage: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 3: { @@ -238,7 +239,7 @@ export const Camouflage: MessageFns = { break; } - message.tank_name = reader.string(); + message.tank_name = I18nString.decode(reader, reader.uint32()); continue; } case 4: { @@ -246,7 +247,7 @@ export const Camouflage: MessageFns = { break; } - message.tank_name_full = reader.string(); + message.tank_name_full = I18nString.decode(reader, reader.uint32()); continue; } } @@ -261,9 +262,9 @@ export const Camouflage: MessageFns = { fromJSON(object: any): Camouflage { return { id: globalThis.Number(assertSet("Camouflage.id", object.id)), - name: globalThis.String(assertSet("Camouflage.name", object.name)), - tank_name: isSet(object.tank_name) ? globalThis.String(object.tank_name) : undefined, - tank_name_full: isSet(object.tank_name_full) ? globalThis.String(object.tank_name_full) : undefined, + name: I18nString.fromJSON(assertSet("Camouflage.name", object.name)), + tank_name: isSet(object.tank_name) ? I18nString.fromJSON(object.tank_name) : undefined, + tank_name_full: isSet(object.tank_name_full) ? I18nString.fromJSON(object.tank_name_full) : undefined, }; }, @@ -272,14 +273,14 @@ export const Camouflage: MessageFns = { if (message.id !== 0) { obj.id = Math.round(message.id); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } - if (message.tank_name !== undefined && message.tank_name !== undefined) { - obj.tank_name = message.tank_name; + if (message.tank_name !== undefined) { + obj.tank_name = I18nString.toJSON(message.tank_name); } - if (message.tank_name_full !== undefined && message.tank_name_full !== undefined) { - obj.tank_name_full = message.tank_name_full; + if (message.tank_name_full !== undefined) { + obj.tank_name_full = I18nString.toJSON(message.tank_name_full); } return obj; }, @@ -290,9 +291,15 @@ export const Camouflage: MessageFns = { fromPartial, I>>(object: I): Camouflage { const message = createBaseCamouflage(); message.id = object.id ?? 0; - message.name = object.name ?? ""; - message.tank_name = object.tank_name ?? undefined; - message.tank_name_full = object.tank_name_full ?? undefined; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); + message.tank_name = (object.tank_name !== undefined && object.tank_name !== null) + ? I18nString.fromPartial(object.tank_name) + : undefined; + message.tank_name_full = (object.tank_name_full !== undefined && object.tank_name_full !== null) + ? I18nString.fromPartial(object.tank_name_full) + : undefined; return message; }, }; diff --git a/packages/core/src/protos/consumable_definitions.proto b/packages/core/src/protos/consumable_definitions.proto index 7d877d942..0d65daaf3 100644 --- a/packages/core/src/protos/consumable_definitions.proto +++ b/packages/core/src/protos/consumable_definitions.proto @@ -2,13 +2,15 @@ syntax = "proto2"; package blitzkit; +import "packages/core/src/protos/i18n.proto"; + message ConsumableDefinitions { map consumables = 1; } message Consumable { required uint32 id = 1; - required string name = 2; + required I18nString name = 2; required uint32 cooldown = 3; optional float duration = 4; required bool game_mode_exclusive = 5; diff --git a/packages/core/src/protos/consumable_definitions.ts b/packages/core/src/protos/consumable_definitions.ts index b1b0256ae..c6e636d8d 100644 --- a/packages/core/src/protos/consumable_definitions.ts +++ b/packages/core/src/protos/consumable_definitions.ts @@ -6,6 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -47,7 +48,7 @@ export interface ConsumableDefinitions_ConsumablesEntry { export interface Consumable { id: number; - name: string; + name: I18nString; cooldown: number; duration?: number | undefined; game_mode_exclusive: boolean; @@ -240,13 +241,21 @@ export const ConsumableDefinitions_ConsumablesEntry: MessageFns = { @@ -254,8 +263,8 @@ export const Consumable: MessageFns = { if (message.id !== 0) { writer.uint32(8).uint32(message.id); } - if (message.name !== "") { - writer.uint32(18).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(18).fork()).join(); } if (message.cooldown !== 0) { writer.uint32(24).uint32(message.cooldown); @@ -295,7 +304,7 @@ export const Consumable: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 3: { @@ -350,7 +359,7 @@ export const Consumable: MessageFns = { fromJSON(object: any): Consumable { return { id: globalThis.Number(assertSet("Consumable.id", object.id)), - name: globalThis.String(assertSet("Consumable.name", object.name)), + name: I18nString.fromJSON(assertSet("Consumable.name", object.name)), cooldown: globalThis.Number(assertSet("Consumable.cooldown", object.cooldown)), duration: isSet(object.duration) ? globalThis.Number(object.duration) : undefined, game_mode_exclusive: globalThis.Boolean(assertSet("Consumable.game_mode_exclusive", object.game_mode_exclusive)), @@ -368,8 +377,8 @@ export const Consumable: MessageFns = { if (message.id !== 0) { obj.id = Math.round(message.id); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.cooldown !== 0) { obj.cooldown = Math.round(message.cooldown); @@ -395,7 +404,9 @@ export const Consumable: MessageFns = { fromPartial, I>>(object: I): Consumable { const message = createBaseConsumable(); message.id = object.id ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.cooldown = object.cooldown ?? 0; message.duration = object.duration ?? undefined; message.game_mode_exclusive = object.game_mode_exclusive ?? false; diff --git a/packages/core/src/protos/equipment_definitions.proto b/packages/core/src/protos/equipment_definitions.proto index c954424d4..536b7c2e0 100644 --- a/packages/core/src/protos/equipment_definitions.proto +++ b/packages/core/src/protos/equipment_definitions.proto @@ -2,14 +2,16 @@ syntax = "proto2"; package blitzkit; +import "packages/core/src/protos/i18n.proto"; + message EquipmentDefinitions { map presets = 1; map equipments = 2; } message Equipment { - required string name = 1; - required string description = 2; + required I18nString name = 1; + required I18nString description = 2; } message EquipmentPreset { diff --git a/packages/core/src/protos/equipment_definitions.ts b/packages/core/src/protos/equipment_definitions.ts index 1bb5d76d4..5583cd976 100644 --- a/packages/core/src/protos/equipment_definitions.ts +++ b/packages/core/src/protos/equipment_definitions.ts @@ -6,6 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -25,8 +26,8 @@ export interface EquipmentDefinitions_EquipmentsEntry { } export interface Equipment { - name: string; - description: string; + name: I18nString; + description: I18nString; } export interface EquipmentPreset { @@ -235,7 +236,7 @@ export const EquipmentDefinitions_PresetsEntry: MessageFns = { encode(message: Equipment, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.name !== "") { - writer.uint32(10).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(10).fork()).join(); } - if (message.description !== "") { - writer.uint32(18).string(message.description); + if (message.description !== undefined) { + I18nString.encode(message.description, writer.uint32(18).fork()).join(); } return writer; }, @@ -349,7 +350,7 @@ export const Equipment: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 2: { @@ -357,7 +358,7 @@ export const Equipment: MessageFns = { break; } - message.description = reader.string(); + message.description = I18nString.decode(reader, reader.uint32()); continue; } } @@ -371,18 +372,18 @@ export const Equipment: MessageFns = { fromJSON(object: any): Equipment { return { - name: globalThis.String(assertSet("Equipment.name", object.name)), - description: globalThis.String(assertSet("Equipment.description", object.description)), + name: I18nString.fromJSON(assertSet("Equipment.name", object.name)), + description: I18nString.fromJSON(assertSet("Equipment.description", object.description)), }; }, toJSON(message: Equipment): unknown { const obj: any = {}; - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } - if (message.description !== "") { - obj.description = message.description; + if (message.description !== undefined) { + obj.description = I18nString.toJSON(message.description); } return obj; }, @@ -392,8 +393,12 @@ export const Equipment: MessageFns = { }, fromPartial, I>>(object: I): Equipment { const message = createBaseEquipment(); - message.name = object.name ?? ""; - message.description = object.description ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); + message.description = (object.description !== undefined && object.description !== null) + ? I18nString.fromPartial(object.description) + : createBaseI18nString(); return message; }, }; diff --git a/packages/core/src/protos/game_definitions.proto b/packages/core/src/protos/game_definitions.proto index 7d3ffcb48..af8dff450 100644 --- a/packages/core/src/protos/game_definitions.proto +++ b/packages/core/src/protos/game_definitions.proto @@ -2,6 +2,8 @@ syntax = "proto2"; package blitzkit; +import "packages/core/src/protos/i18n.proto"; + message GameDefinitions { required string version = 1; repeated string nations = 2; @@ -10,7 +12,7 @@ message GameDefinitions { } message GameMode { - required string name = 1; + required I18nString name = 1; } message GameModeRole { diff --git a/packages/core/src/protos/game_definitions.ts b/packages/core/src/protos/game_definitions.ts index 089a1cf43..81af446a6 100644 --- a/packages/core/src/protos/game_definitions.ts +++ b/packages/core/src/protos/game_definitions.ts @@ -6,6 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -27,7 +28,7 @@ export interface GameDefinitions_RolesEntry { } export interface GameMode { - name: string; + name: I18nString; } export interface GameModeRole { @@ -259,7 +260,7 @@ export const GameDefinitions_GameModesEntry: MessageFns message.key = object.key ?? 0; message.value = (object.value !== undefined && object.value !== null) ? GameModeRole.fromPartial(object.value) - : createBaseGameModeRole(); + : undefined; return message; }, }; function createBaseGameMode(): GameMode { - return { name: "" }; + return { name: createBaseI18nString() }; } export const GameMode: MessageFns = { encode(message: GameMode, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.name !== "") { - writer.uint32(10).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(10).fork()).join(); } return writer; }, @@ -366,7 +367,7 @@ export const GameMode: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } } @@ -379,13 +380,13 @@ export const GameMode: MessageFns = { }, fromJSON(object: any): GameMode { - return { name: globalThis.String(assertSet("GameMode.name", object.name)) }; + return { name: I18nString.fromJSON(assertSet("GameMode.name", object.name)) }; }, toJSON(message: GameMode): unknown { const obj: any = {}; - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } return obj; }, @@ -395,7 +396,9 @@ export const GameMode: MessageFns = { }, fromPartial, I>>(object: I): GameMode { const message = createBaseGameMode(); - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); return message; }, }; diff --git a/packages/core/src/protos/i18n.proto b/packages/core/src/protos/i18n.proto new file mode 100644 index 000000000..2c31070df --- /dev/null +++ b/packages/core/src/protos/i18n.proto @@ -0,0 +1,7 @@ +syntax = "proto2"; + +package blitzkit; + +message I18nString { + map locales = 1; +} \ No newline at end of file diff --git a/packages/core/src/protos/i18n.ts b/packages/core/src/protos/i18n.ts new file mode 100644 index 000000000..21d5ca7f0 --- /dev/null +++ b/packages/core/src/protos/i18n.ts @@ -0,0 +1,212 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.2.2 +// protoc v5.28.2 +// source: packages/core/src/protos/i18n.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "blitzkit"; + +export interface I18nString { + locales: { [key: string]: string }; +} + +export interface I18nString_LocalesEntry { + key: string; + value: string; +} + +function createBaseI18nString(): I18nString { + return { locales: {} }; +} + +export const I18nString: MessageFns = { + encode(message: I18nString, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + Object.entries(message.locales).forEach(([key, value]) => { + I18nString_LocalesEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).join(); + }); + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): I18nString { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseI18nString(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + const entry1 = I18nString_LocalesEntry.decode(reader, reader.uint32()); + if (entry1.value !== undefined) { + message.locales[entry1.key] = entry1.value; + } + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): I18nString { + return { + locales: isObject(object.locales) + ? Object.entries(object.locales).reduce<{ [key: string]: string }>((acc, [key, value]) => { + acc[key] = String(value); + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: I18nString): unknown { + const obj: any = {}; + if (message.locales) { + const entries = Object.entries(message.locales); + if (entries.length > 0) { + obj.locales = {}; + entries.forEach(([k, v]) => { + obj.locales[k] = v; + }); + } + } + return obj; + }, + + create, I>>(base?: I): I18nString { + return I18nString.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): I18nString { + const message = createBaseI18nString(); + message.locales = Object.entries(object.locales ?? {}).reduce<{ [key: string]: string }>((acc, [key, value]) => { + if (value !== undefined) { + acc[key] = globalThis.String(value); + } + return acc; + }, {}); + return message; + }, +}; + +function createBaseI18nString_LocalesEntry(): I18nString_LocalesEntry { + return { key: "", value: "" }; +} + +export const I18nString_LocalesEntry: MessageFns = { + encode(message: I18nString_LocalesEntry, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.key !== undefined) { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + writer.uint32(18).string(message.value); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): I18nString_LocalesEntry { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseI18nString_LocalesEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.value = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): I18nString_LocalesEntry { + return { + key: globalThis.String(assertSet("I18nString_LocalesEntry.key", object.key)), + value: globalThis.String(assertSet("I18nString_LocalesEntry.value", object.value)), + }; + }, + + toJSON(message: I18nString_LocalesEntry): unknown { + const obj: any = {}; + if (message.key !== undefined) { + obj.key = message.key; + } + if (message.value !== undefined) { + obj.value = message.value; + } + return obj; + }, + + create, I>>(base?: I): I18nString_LocalesEntry { + return I18nString_LocalesEntry.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): I18nString_LocalesEntry { + const message = createBaseI18nString_LocalesEntry(); + message.key = object.key ?? ""; + message.value = object.value ?? ""; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends { $case: string; value: unknown } ? { $case: T["$case"]; value?: DeepPartial } + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +function assertSet(field: string, value: T | undefined): T { + if (!isSet(value)) { + throw new TypeError(`Required field ${field} is not set`); + } + + return value as T; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/packages/core/src/protos/index.ts b/packages/core/src/protos/index.ts index 29b3b3368..a0b2eb4fc 100644 --- a/packages/core/src/protos/index.ts +++ b/packages/core/src/protos/index.ts @@ -4,6 +4,7 @@ export * from './camouflage_definitions.ts'; export * from './consumable_definitions.ts'; export * from './equipment_definitions.ts'; export * from './game_definitions.ts'; +export * from './i18n.ts'; export * from './map_definitions.ts'; export * from './model_definitions.ts'; export * from './provision_definitions.ts'; diff --git a/packages/core/src/protos/map_definitions.proto b/packages/core/src/protos/map_definitions.proto index 27c11112e..1d67d331a 100644 --- a/packages/core/src/protos/map_definitions.proto +++ b/packages/core/src/protos/map_definitions.proto @@ -2,11 +2,13 @@ syntax = "proto2"; package blitzkit; +import "packages/core/src/protos/i18n.proto"; + message MapDefinitions { map maps = 1; } message MapDefinition { required uint32 id = 1; - required string name = 2; + required I18nString name = 2; } \ No newline at end of file diff --git a/packages/core/src/protos/map_definitions.ts b/packages/core/src/protos/map_definitions.ts index b0228f2d2..35fa0de9f 100644 --- a/packages/core/src/protos/map_definitions.ts +++ b/packages/core/src/protos/map_definitions.ts @@ -6,6 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -20,7 +21,7 @@ export interface MapDefinitions_MapsEntry { export interface MapDefinition { id: number; - name: string; + name: I18nString; } function createBaseMapDefinitions(): MapDefinitions { @@ -175,13 +176,13 @@ export const MapDefinitions_MapsEntry: MessageFns = { message.key = object.key ?? 0; message.value = (object.value !== undefined && object.value !== null) ? MapDefinition.fromPartial(object.value) - : createBaseMapDefinition(); + : undefined; return message; }, }; function createBaseMapDefinition(): MapDefinition { - return { id: 0, name: "" }; + return { id: 0, name: createBaseI18nString() }; } export const MapDefinition: MessageFns = { @@ -189,8 +190,8 @@ export const MapDefinition: MessageFns = { if (message.id !== 0) { writer.uint32(8).uint32(message.id); } - if (message.name !== "") { - writer.uint32(18).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(18).fork()).join(); } return writer; }, @@ -215,7 +216,7 @@ export const MapDefinition: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } } @@ -230,7 +231,7 @@ export const MapDefinition: MessageFns = { fromJSON(object: any): MapDefinition { return { id: globalThis.Number(assertSet("MapDefinition.id", object.id)), - name: globalThis.String(assertSet("MapDefinition.name", object.name)), + name: I18nString.fromJSON(assertSet("MapDefinition.name", object.name)), }; }, @@ -239,8 +240,8 @@ export const MapDefinition: MessageFns = { if (message.id !== 0) { obj.id = Math.round(message.id); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } return obj; }, @@ -251,7 +252,9 @@ export const MapDefinition: MessageFns = { fromPartial, I>>(object: I): MapDefinition { const message = createBaseMapDefinition(); message.id = object.id ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); return message; }, }; diff --git a/packages/core/src/protos/model_definitions.ts b/packages/core/src/protos/model_definitions.ts index e864ea44e..3adb2b49a 100644 --- a/packages/core/src/protos/model_definitions.ts +++ b/packages/core/src/protos/model_definitions.ts @@ -265,7 +265,7 @@ export const ModelDefinitions_ModelsEntry: MessageFns provisions = 1; @@ -10,7 +11,7 @@ message ProvisionDefinitions { message Provision { required uint32 id = 1; - required string name = 2; + required I18nString name = 2; optional float crew = 3; required bool game_mode_exclusive = 5; repeated ConsumableTankFilter include = 6; diff --git a/packages/core/src/protos/provision_definitions.ts b/packages/core/src/protos/provision_definitions.ts index 9fd678ff7..7026c5ab4 100644 --- a/packages/core/src/protos/provision_definitions.ts +++ b/packages/core/src/protos/provision_definitions.ts @@ -7,6 +7,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; import { ConsumableTankFilter } from "./consumable_definitions"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -21,7 +22,7 @@ export interface ProvisionDefinitions_ProvisionsEntry { export interface Provision { id: number; - name: string; + name: I18nString; crew?: number | undefined; game_mode_exclusive: boolean; include: ConsumableTankFilter[]; @@ -187,13 +188,13 @@ export const ProvisionDefinitions_ProvisionsEntry: MessageFns = { @@ -201,8 +202,8 @@ export const Provision: MessageFns = { if (message.id !== 0) { writer.uint32(8).uint32(message.id); } - if (message.name !== "") { - writer.uint32(18).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(18).fork()).join(); } if (message.crew !== undefined && message.crew !== undefined) { writer.uint32(29).float(message.crew); @@ -239,7 +240,7 @@ export const Provision: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 3: { @@ -286,7 +287,7 @@ export const Provision: MessageFns = { fromJSON(object: any): Provision { return { id: globalThis.Number(assertSet("Provision.id", object.id)), - name: globalThis.String(assertSet("Provision.name", object.name)), + name: I18nString.fromJSON(assertSet("Provision.name", object.name)), crew: isSet(object.crew) ? globalThis.Number(object.crew) : undefined, game_mode_exclusive: globalThis.Boolean(assertSet("Provision.game_mode_exclusive", object.game_mode_exclusive)), include: globalThis.Array.isArray(object?.include) @@ -303,8 +304,8 @@ export const Provision: MessageFns = { if (message.id !== 0) { obj.id = Math.round(message.id); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.crew !== undefined && message.crew !== undefined) { obj.crew = message.crew; @@ -327,7 +328,9 @@ export const Provision: MessageFns = { fromPartial, I>>(object: I): Provision { const message = createBaseProvision(); message.id = object.id ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.crew = object.crew ?? undefined; message.game_mode_exclusive = object.game_mode_exclusive ?? false; message.include = object.include?.map((e) => ConsumableTankFilter.fromPartial(e)) || []; diff --git a/packages/core/src/protos/reviews.ts b/packages/core/src/protos/reviews.ts index c98358f06..4081dafb5 100644 --- a/packages/core/src/protos/reviews.ts +++ b/packages/core/src/protos/reviews.ts @@ -180,7 +180,7 @@ export const Reviews_ReviewsEntry: MessageFns = { message.key = object.key ?? 0; message.value = (object.value !== undefined && object.value !== null) ? Review.fromPartial(object.value) - : createBaseReview(); + : undefined; return message; }, }; diff --git a/packages/core/src/protos/skill_definitions.ts b/packages/core/src/protos/skill_definitions.ts index b371fb625..e59372ba3 100644 --- a/packages/core/src/protos/skill_definitions.ts +++ b/packages/core/src/protos/skill_definitions.ts @@ -175,9 +175,7 @@ export const SkillDefinitions_ClassesEntry: MessageFns tanks = 1; } @@ -9,7 +11,6 @@ message TankDefinitions { message TankDefinition { required uint32 id = 1; map roles = 2; - optional string description = 3; required bool fixed_camouflage = 4; repeated int32 camouflages = 5; repeated int32 ancestors = 6; @@ -17,8 +18,8 @@ message TankDefinition { repeated Crew crew = 8; required uint32 health = 9; required string nation = 10; - required string name = 11; - optional string name_full = 12; + required I18nString name = 11; + optional I18nString name_full = 12; required TankType type = 13; required uint32 max_consumables = 14; required uint32 max_provisions = 15; @@ -65,7 +66,7 @@ message TankPrice { message TrackDefinition { required uint32 id = 1; required uint32 tier = 2; - required string name = 3; + required I18nString name = 3; required uint32 weight = 4; required float traverse_speed = 5; optional ResearchCost research_cost = 6; @@ -79,7 +80,7 @@ message TrackDefinition { message EngineDefinition { required uint32 id = 1; - required string name = 2; + required I18nString name = 2; optional ResearchCost research_cost = 3; required uint32 tier = 4; required float fire_chance = 5; @@ -94,7 +95,7 @@ message TurretDefinition { required uint32 view_range = 3; required float traverse_speed = 4; optional ResearchCost research_cost = 5; - required string name = 6; + required I18nString name = 6; required uint32 tier = 7; required uint32 weight = 8; repeated GunDefinition guns = 9; @@ -145,7 +146,7 @@ message GunDefinitionBase { required float rotation_speed = 2; optional ResearchCost research_cost = 3; required uint32 weight = 4; - required string name = 5; + required I18nString name = 5; required uint32 tier = 6; repeated ShellDefinition shells = 7; required float camouflage_loss = 8; @@ -160,7 +161,7 @@ message GunDefinitionBase { message ShellDefinition { required uint32 id = 1; - required string name = 2; + required I18nString name = 2; required uint32 velocity = 3; required uint32 armor_damage = 4; required uint32 module_damage = 5; diff --git a/packages/core/src/protos/tank_definitions.ts b/packages/core/src/protos/tank_definitions.ts index 38eb5cac6..12c79ca20 100644 --- a/packages/core/src/protos/tank_definitions.ts +++ b/packages/core/src/protos/tank_definitions.ts @@ -6,6 +6,7 @@ /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { I18nString } from "./i18n"; export const protobufPackage = "blitzkit"; @@ -261,7 +262,6 @@ export interface TankDefinitions_TanksEntry { export interface TankDefinition { id: number; roles: { [key: number]: number }; - description?: string | undefined; fixed_camouflage: boolean; camouflages: number[]; ancestors: number[]; @@ -269,8 +269,8 @@ export interface TankDefinition { crew: Crew[]; health: number; nation: string; - name: string; - name_full?: string | undefined; + name: I18nString; + name_full?: I18nString | undefined; type: TankType; max_consumables: number; max_provisions: number; @@ -317,7 +317,7 @@ export interface TankPrice { export interface TrackDefinition { id: number; tier: number; - name: string; + name: I18nString; weight: number; traverse_speed: number; research_cost?: ResearchCost | undefined; @@ -331,7 +331,7 @@ export interface TrackDefinition { export interface EngineDefinition { id: number; - name: string; + name: I18nString; research_cost?: ResearchCost | undefined; tier: number; fire_chance: number; @@ -346,7 +346,7 @@ export interface TurretDefinition { view_range: number; traverse_speed: number; research_cost?: ResearchCost | undefined; - name: string; + name: I18nString; tier: number; weight: number; guns: GunDefinition[]; @@ -397,7 +397,7 @@ export interface GunDefinitionBase { rotation_speed: number; research_cost?: ResearchCost | undefined; weight: number; - name: string; + name: I18nString; tier: number; shells: ShellDefinition[]; camouflage_loss: number; @@ -412,7 +412,7 @@ export interface GunDefinitionBase { export interface ShellDefinition { id: number; - name: string; + name: I18nString; velocity: number; armor_damage: number; module_damage: number; @@ -603,7 +603,7 @@ export const TankDefinitions_TanksEntry: MessageFns message.key = object.key ?? 0; message.value = (object.value !== undefined && object.value !== null) ? TankDefinition.fromPartial(object.value) - : createBaseTankDefinition(); + : undefined; return message; }, }; @@ -612,7 +612,6 @@ function createBaseTankDefinition(): TankDefinition { return { id: 0, roles: {}, - description: undefined, fixed_camouflage: false, camouflages: [], ancestors: [], @@ -620,7 +619,7 @@ function createBaseTankDefinition(): TankDefinition { crew: [], health: 0, nation: "", - name: "", + name: createBaseI18nString(), name_full: undefined, type: 0, max_consumables: 0, @@ -652,9 +651,6 @@ export const TankDefinition: MessageFns = { Object.entries(message.roles).forEach(([key, value]) => { TankDefinition_RolesEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).join(); }); - if (message.description !== undefined && message.description !== undefined) { - writer.uint32(26).string(message.description); - } if (message.fixed_camouflage !== false) { writer.uint32(32).bool(message.fixed_camouflage); } @@ -682,11 +678,11 @@ export const TankDefinition: MessageFns = { if (message.nation !== "") { writer.uint32(82).string(message.nation); } - if (message.name !== "") { - writer.uint32(90).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(90).fork()).join(); } - if (message.name_full !== undefined && message.name_full !== undefined) { - writer.uint32(98).string(message.name_full); + if (message.name_full !== undefined) { + I18nString.encode(message.name_full, writer.uint32(98).fork()).join(); } if (message.type !== 0) { writer.uint32(104).int32(message.type); @@ -774,14 +770,6 @@ export const TankDefinition: MessageFns = { } continue; } - case 3: { - if (tag !== 26) { - break; - } - - message.description = reader.string(); - continue; - } case 4: { if (tag !== 32) { break; @@ -873,7 +861,7 @@ export const TankDefinition: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 12: { @@ -881,7 +869,7 @@ export const TankDefinition: MessageFns = { break; } - message.name_full = reader.string(); + message.name_full = I18nString.decode(reader, reader.uint32()); continue; } case 13: { @@ -1054,7 +1042,6 @@ export const TankDefinition: MessageFns = { return acc; }, {}) : {}, - description: isSet(object.description) ? globalThis.String(object.description) : undefined, fixed_camouflage: globalThis.Boolean(assertSet("TankDefinition.fixed_camouflage", object.fixed_camouflage)), camouflages: globalThis.Array.isArray(object?.camouflages) ? object.camouflages.map((e: any) => globalThis.Number(e)) @@ -1068,8 +1055,8 @@ export const TankDefinition: MessageFns = { crew: globalThis.Array.isArray(object?.crew) ? object.crew.map((e: any) => Crew.fromJSON(e)) : [], health: globalThis.Number(assertSet("TankDefinition.health", object.health)), nation: globalThis.String(assertSet("TankDefinition.nation", object.nation)), - name: globalThis.String(assertSet("TankDefinition.name", object.name)), - name_full: isSet(object.name_full) ? globalThis.String(object.name_full) : undefined, + name: I18nString.fromJSON(assertSet("TankDefinition.name", object.name)), + name_full: isSet(object.name_full) ? I18nString.fromJSON(object.name_full) : undefined, type: tankTypeFromJSON(assertSet("TankDefinition.type", object.type)), max_consumables: globalThis.Number(assertSet("TankDefinition.max_consumables", object.max_consumables)), max_provisions: globalThis.Number(assertSet("TankDefinition.max_provisions", object.max_provisions)), @@ -1112,9 +1099,6 @@ export const TankDefinition: MessageFns = { }); } } - if (message.description !== undefined && message.description !== undefined) { - obj.description = message.description; - } if (message.fixed_camouflage !== false) { obj.fixed_camouflage = message.fixed_camouflage; } @@ -1136,11 +1120,11 @@ export const TankDefinition: MessageFns = { if (message.nation !== "") { obj.nation = message.nation; } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } - if (message.name_full !== undefined && message.name_full !== undefined) { - obj.name_full = message.name_full; + if (message.name_full !== undefined) { + obj.name_full = I18nString.toJSON(message.name_full); } if (message.type !== 0) { obj.type = tankTypeToJSON(message.type); @@ -1214,7 +1198,6 @@ export const TankDefinition: MessageFns = { } return acc; }, {}); - message.description = object.description ?? undefined; message.fixed_camouflage = object.fixed_camouflage ?? false; message.camouflages = object.camouflages?.map((e) => e) || []; message.ancestors = object.ancestors?.map((e) => e) || []; @@ -1222,8 +1205,12 @@ export const TankDefinition: MessageFns = { message.crew = object.crew?.map((e) => Crew.fromPartial(e)) || []; message.health = object.health ?? 0; message.nation = object.nation ?? ""; - message.name = object.name ?? ""; - message.name_full = object.name_full ?? undefined; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); + message.name_full = (object.name_full !== undefined && object.name_full !== null) + ? I18nString.fromPartial(object.name_full) + : undefined; message.type = object.type ?? 0; message.max_consumables = object.max_consumables ?? 0; message.max_provisions = object.max_provisions ?? 0; @@ -1582,7 +1569,7 @@ function createBaseTrackDefinition(): TrackDefinition { return { id: 0, tier: 0, - name: "", + name: createBaseI18nString(), weight: 0, traverse_speed: 0, research_cost: undefined, @@ -1603,8 +1590,8 @@ export const TrackDefinition: MessageFns = { if (message.tier !== 0) { writer.uint32(16).uint32(message.tier); } - if (message.name !== "") { - writer.uint32(26).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(26).fork()).join(); } if (message.weight !== 0) { writer.uint32(32).uint32(message.weight); @@ -1664,7 +1651,7 @@ export const TrackDefinition: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 4: { @@ -1752,7 +1739,7 @@ export const TrackDefinition: MessageFns = { return { id: globalThis.Number(assertSet("TrackDefinition.id", object.id)), tier: globalThis.Number(assertSet("TrackDefinition.tier", object.tier)), - name: globalThis.String(assertSet("TrackDefinition.name", object.name)), + name: I18nString.fromJSON(assertSet("TrackDefinition.name", object.name)), weight: globalThis.Number(assertSet("TrackDefinition.weight", object.weight)), traverse_speed: globalThis.Number(assertSet("TrackDefinition.traverse_speed", object.traverse_speed)), research_cost: isSet(object.research_cost) ? ResearchCost.fromJSON(object.research_cost) : undefined, @@ -1775,8 +1762,8 @@ export const TrackDefinition: MessageFns = { if (message.tier !== 0) { obj.tier = Math.round(message.tier); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.weight !== 0) { obj.weight = Math.round(message.weight); @@ -1815,7 +1802,9 @@ export const TrackDefinition: MessageFns = { const message = createBaseTrackDefinition(); message.id = object.id ?? 0; message.tier = object.tier ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.weight = object.weight ?? 0; message.traverse_speed = object.traverse_speed ?? 0; message.research_cost = (object.research_cost !== undefined && object.research_cost !== null) @@ -1832,7 +1821,16 @@ export const TrackDefinition: MessageFns = { }; function createBaseEngineDefinition(): EngineDefinition { - return { id: 0, name: "", research_cost: undefined, tier: 0, fire_chance: 0, power: 0, weight: 0, unlocks: [] }; + return { + id: 0, + name: createBaseI18nString(), + research_cost: undefined, + tier: 0, + fire_chance: 0, + power: 0, + weight: 0, + unlocks: [], + }; } export const EngineDefinition: MessageFns = { @@ -1840,8 +1838,8 @@ export const EngineDefinition: MessageFns = { if (message.id !== 0) { writer.uint32(8).uint32(message.id); } - if (message.name !== "") { - writer.uint32(18).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(18).fork()).join(); } if (message.research_cost !== undefined) { ResearchCost.encode(message.research_cost, writer.uint32(26).fork()).join(); @@ -1884,7 +1882,7 @@ export const EngineDefinition: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 3: { @@ -1947,7 +1945,7 @@ export const EngineDefinition: MessageFns = { fromJSON(object: any): EngineDefinition { return { id: globalThis.Number(assertSet("EngineDefinition.id", object.id)), - name: globalThis.String(assertSet("EngineDefinition.name", object.name)), + name: I18nString.fromJSON(assertSet("EngineDefinition.name", object.name)), research_cost: isSet(object.research_cost) ? ResearchCost.fromJSON(object.research_cost) : undefined, tier: globalThis.Number(assertSet("EngineDefinition.tier", object.tier)), fire_chance: globalThis.Number(assertSet("EngineDefinition.fire_chance", object.fire_chance)), @@ -1962,8 +1960,8 @@ export const EngineDefinition: MessageFns = { if (message.id !== 0) { obj.id = Math.round(message.id); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.research_cost !== undefined) { obj.research_cost = ResearchCost.toJSON(message.research_cost); @@ -1992,7 +1990,9 @@ export const EngineDefinition: MessageFns = { fromPartial, I>>(object: I): EngineDefinition { const message = createBaseEngineDefinition(); message.id = object.id ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.research_cost = (object.research_cost !== undefined && object.research_cost !== null) ? ResearchCost.fromPartial(object.research_cost) : undefined; @@ -2012,7 +2012,7 @@ function createBaseTurretDefinition(): TurretDefinition { view_range: 0, traverse_speed: 0, research_cost: undefined, - name: "", + name: createBaseI18nString(), tier: 0, weight: 0, guns: [], @@ -2037,8 +2037,8 @@ export const TurretDefinition: MessageFns = { if (message.research_cost !== undefined) { ResearchCost.encode(message.research_cost, writer.uint32(42).fork()).join(); } - if (message.name !== "") { - writer.uint32(50).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(50).fork()).join(); } if (message.tier !== 0) { writer.uint32(56).uint32(message.tier); @@ -2107,7 +2107,7 @@ export const TurretDefinition: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 7: { @@ -2158,7 +2158,7 @@ export const TurretDefinition: MessageFns = { view_range: globalThis.Number(assertSet("TurretDefinition.view_range", object.view_range)), traverse_speed: globalThis.Number(assertSet("TurretDefinition.traverse_speed", object.traverse_speed)), research_cost: isSet(object.research_cost) ? ResearchCost.fromJSON(object.research_cost) : undefined, - name: globalThis.String(assertSet("TurretDefinition.name", object.name)), + name: I18nString.fromJSON(assertSet("TurretDefinition.name", object.name)), tier: globalThis.Number(assertSet("TurretDefinition.tier", object.tier)), weight: globalThis.Number(assertSet("TurretDefinition.weight", object.weight)), guns: globalThis.Array.isArray(object?.guns) ? object.guns.map((e: any) => GunDefinition.fromJSON(e)) : [], @@ -2183,8 +2183,8 @@ export const TurretDefinition: MessageFns = { if (message.research_cost !== undefined) { obj.research_cost = ResearchCost.toJSON(message.research_cost); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.tier !== 0) { obj.tier = Math.round(message.tier); @@ -2213,7 +2213,9 @@ export const TurretDefinition: MessageFns = { message.research_cost = (object.research_cost !== undefined && object.research_cost !== null) ? ResearchCost.fromPartial(object.research_cost) : undefined; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.tier = object.tier ?? 0; message.weight = object.weight ?? 0; message.guns = object.guns?.map((e) => GunDefinition.fromPartial(e)) || []; @@ -2858,7 +2860,7 @@ function createBaseGunDefinitionBase(): GunDefinitionBase { rotation_speed: 0, research_cost: undefined, weight: 0, - name: "", + name: createBaseI18nString(), tier: 0, shells: [], camouflage_loss: 0, @@ -2886,8 +2888,8 @@ export const GunDefinitionBase: MessageFns = { if (message.weight !== 0) { writer.uint32(32).uint32(message.weight); } - if (message.name !== "") { - writer.uint32(42).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(42).fork()).join(); } if (message.tier !== 0) { writer.uint32(48).uint32(message.tier); @@ -2966,7 +2968,7 @@ export const GunDefinitionBase: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 6: { @@ -3064,7 +3066,7 @@ export const GunDefinitionBase: MessageFns = { rotation_speed: globalThis.Number(assertSet("GunDefinitionBase.rotation_speed", object.rotation_speed)), research_cost: isSet(object.research_cost) ? ResearchCost.fromJSON(object.research_cost) : undefined, weight: globalThis.Number(assertSet("GunDefinitionBase.weight", object.weight)), - name: globalThis.String(assertSet("GunDefinitionBase.name", object.name)), + name: I18nString.fromJSON(assertSet("GunDefinitionBase.name", object.name)), tier: globalThis.Number(assertSet("GunDefinitionBase.tier", object.tier)), shells: globalThis.Array.isArray(object?.shells) ? object.shells.map((e: any) => ShellDefinition.fromJSON(e)) @@ -3098,8 +3100,8 @@ export const GunDefinitionBase: MessageFns = { if (message.weight !== 0) { obj.weight = Math.round(message.weight); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.tier !== 0) { obj.tier = Math.round(message.tier); @@ -3145,7 +3147,9 @@ export const GunDefinitionBase: MessageFns = { ? ResearchCost.fromPartial(object.research_cost) : undefined; message.weight = object.weight ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.tier = object.tier ?? 0; message.shells = object.shells?.map((e) => ShellDefinition.fromPartial(e)) || []; message.camouflage_loss = object.camouflage_loss ?? 0; @@ -3163,7 +3167,7 @@ export const GunDefinitionBase: MessageFns = { function createBaseShellDefinition(): ShellDefinition { return { id: 0, - name: "", + name: createBaseI18nString(), velocity: 0, armor_damage: 0, module_damage: 0, @@ -3183,8 +3187,8 @@ export const ShellDefinition: MessageFns = { if (message.id !== 0) { writer.uint32(8).uint32(message.id); } - if (message.name !== "") { - writer.uint32(18).string(message.name); + if (message.name !== undefined) { + I18nString.encode(message.name, writer.uint32(18).fork()).join(); } if (message.velocity !== 0) { writer.uint32(24).uint32(message.velocity); @@ -3242,7 +3246,7 @@ export const ShellDefinition: MessageFns = { break; } - message.name = reader.string(); + message.name = I18nString.decode(reader, reader.uint32()); continue; } case 3: { @@ -3345,7 +3349,7 @@ export const ShellDefinition: MessageFns = { fromJSON(object: any): ShellDefinition { return { id: globalThis.Number(assertSet("ShellDefinition.id", object.id)), - name: globalThis.String(assertSet("ShellDefinition.name", object.name)), + name: I18nString.fromJSON(assertSet("ShellDefinition.name", object.name)), velocity: globalThis.Number(assertSet("ShellDefinition.velocity", object.velocity)), armor_damage: globalThis.Number(assertSet("ShellDefinition.armor_damage", object.armor_damage)), module_damage: globalThis.Number(assertSet("ShellDefinition.module_damage", object.module_damage)), @@ -3365,8 +3369,8 @@ export const ShellDefinition: MessageFns = { if (message.id !== 0) { obj.id = Math.round(message.id); } - if (message.name !== "") { - obj.name = message.name; + if (message.name !== undefined) { + obj.name = I18nString.toJSON(message.name); } if (message.velocity !== 0) { obj.velocity = Math.round(message.velocity); @@ -3410,7 +3414,9 @@ export const ShellDefinition: MessageFns = { fromPartial, I>>(object: I): ShellDefinition { const message = createBaseShellDefinition(); message.id = object.id ?? 0; - message.name = object.name ?? ""; + message.name = (object.name !== undefined && object.name !== null) + ? I18nString.fromPartial(object.name) + : createBaseI18nString(); message.velocity = object.velocity ?? 0; message.armor_damage = object.armor_damage ?? 0; message.module_damage = object.module_damage ?? 0; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 52d43eaaa..2ee5d8b21 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src/**/*"] + "include": ["src/**/*", "../i18n/src/strings.ts"] } diff --git a/packages/i18n/package.json b/packages/i18n/package.json new file mode 100644 index 000000000..5704ae2e9 --- /dev/null +++ b/packages/i18n/package.json @@ -0,0 +1,12 @@ +{ + "name": "@blitzkit/i18n", + "private": true, + "type": "module", + "main": "src/index.ts", + "dependencies": { + }, + "devDependencies": { + "@types/adm-zip": "^0.5.5", + "@types/lz4js": "^0.2.1" + } +} diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts new file mode 100644 index 000000000..916b6e9f8 --- /dev/null +++ b/packages/i18n/src/index.ts @@ -0,0 +1 @@ +export * from './strings'; diff --git a/packages/core/src/i18n/strings.ts b/packages/i18n/src/strings.ts similarity index 85% rename from packages/core/src/i18n/strings.ts rename to packages/i18n/src/strings.ts index 17e7d0b46..7e80cbf94 100644 --- a/packages/core/src/i18n/strings.ts +++ b/packages/i18n/src/strings.ts @@ -1,7 +1,10 @@ +import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/core/src/blitzkit'; import { merge } from 'lodash-es'; -import type en from '../../lang/en.json'; -import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../blitzkit'; -import { TranslationTree } from './translator'; +import type en from '../strings/en.json'; + +export type TranslationTree = { [key: string]: TranslationNode }; + +export type TranslationNode = string | TranslationTree; const files = import.meta.glob('../../lang/*.json', { eager: true }); export const localizedStrings: Record = {}; diff --git a/packages/core/lang/en.json b/packages/i18n/strings/en.json similarity index 100% rename from packages/core/lang/en.json rename to packages/i18n/strings/en.json diff --git a/packages/core/lang/es.json b/packages/i18n/strings/es.json similarity index 100% rename from packages/core/lang/es.json rename to packages/i18n/strings/es.json diff --git a/packages/core/lang/fr.json b/packages/i18n/strings/fr.json similarity index 100% rename from packages/core/lang/fr.json rename to packages/i18n/strings/fr.json diff --git a/packages/core/lang/ja.json b/packages/i18n/strings/ja.json similarity index 100% rename from packages/core/lang/ja.json rename to packages/i18n/strings/ja.json diff --git a/packages/core/lang/pt.json b/packages/i18n/strings/pt.json similarity index 100% rename from packages/core/lang/pt.json rename to packages/i18n/strings/pt.json diff --git a/packages/core/lang/ru.json b/packages/i18n/strings/ru.json similarity index 100% rename from packages/core/lang/ru.json rename to packages/i18n/strings/ru.json diff --git a/packages/core/lang/uk.json b/packages/i18n/strings/uk.json similarity index 100% rename from packages/core/lang/uk.json rename to packages/i18n/strings/uk.json diff --git a/packages/core/lang/zh.json b/packages/i18n/strings/zh.json similarity index 100% rename from packages/core/lang/zh.json rename to packages/i18n/strings/zh.json diff --git a/packages/i18n/vite-env.d.ts b/packages/i18n/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/i18n/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/scripts/package.json b/packages/scripts/package.json index f62b1ebb5..d9d521cb1 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@blitzkit/core": "workspace:*", + "@blitzkit/i18n": "workspace:*", "@gltf-transform/core": "^4.0.10", "@gltf-transform/extensions": "^4.0.10", "@gltf-transform/functions": "^4.0.10", diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index d356268bf..4f3b680d3 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -1,6 +1,5 @@ import { Armor, - assertSecret, BlitzTankClass, CamouflageDefinitions, Consumable, @@ -8,12 +7,14 @@ import { ConsumableTankCategoryFilterCategory, Crew, CrewType, + DEFAULT_LOCALE, encodePBBuffer, EquipmentDefinitions, EquipmentSlot, GameDefinitions, GunDefinition, GunDefinitionBase, + I18nString, MapDefinitions, ModelDefinitions, ModuleType, @@ -22,6 +23,8 @@ import { ResearchCost, ShellType, SkillDefinitions, + SUPPORTED_LOCALE_BLITZ_MAP, + SUPPORTED_LOCALES, TankClass, TankDefinitions, TankPrice, @@ -212,7 +215,7 @@ interface VehicleDefinitions { circularVisionRadius: number; maxHealth: number; armor: VehicleDefinitionArmor; - userString: number; + userString: string; level: number; yawLimits: string | string[]; gunPosition: string | string[]; @@ -497,9 +500,6 @@ const blitzShellKindToBlitzkit: Record = { HIGH_EXPLOSIVE: ShellType.HE, HOLLOW_CHARGE: ShellType.HEAT, }; -const missingStrings: Record = { - '#artefacts:tungstentip/name': 'Tungsten Shells', -}; export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/; const blitzModuleTypeToBlitzkit: Record = { chassis: ModuleType.TRACKS, @@ -512,14 +512,6 @@ const blitzModuleTypeToBlitzkit: Record = { export async function definitions() { console.log('Building definitions...'); - const wargamingTankopedia = (await fetch( - `https://api.wotblitz.com/wotb/encyclopedia/vehicles/?application_id=${assertSecret( - import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID, - )}&fields=description`, - ).then((response) => response.json())) as { - data: { [key: number]: { description: null | string } }; - }; - const gameDefinitions: GameDefinitions = { version: (await readStringDVPL(`${DATA}/version.txt.dvpl`)).split(' ')[0], nations: ( @@ -552,9 +544,6 @@ export async function definitions() { (nations) => nations.filter((nation) => nation !== 'common'), ); const tankStringIdMap: Record = {}; - const stringsPreInstalled = await readYAMLDVPL( - `${DATA}/Strings/en.yaml.dvpl`, - ); const stringsCache = await fetch( 'https://stufficons.wgcdn.co/localizations/en.yaml', ) @@ -563,10 +552,46 @@ export async function definitions() { const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>( `${DATA}/XML/item_defs/vehicles/common/optional_devices.xml.dvpl`, ); - const strings = { - ...stringsPreInstalled, - ...stringsCache, - }; + 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`, + ); + + stringsI18n[locale] = { + ...cache, + ...preInstalled, + }; + } + + function getString(name: string) { + const locales: Record = { + [DEFAULT_LOCALE]: stringsI18n[DEFAULT_LOCALE][name], + }; + + for (const locale of SUPPORTED_LOCALES) { + const localizedString = stringsI18n[locale][name]; + + if ( + localizedString === undefined || + localizedString === locales[DEFAULT_LOCALE] + ) { + continue; + } + + locales[locale] = localizedString; + } + + return { locales } satisfies I18nString; + } + const optionalDeviceSlots = await readXMLDVPL<{ root: OptionalDeviceSlots; }>(`${DATA}/XML/item_defs/vehicles/common/optional_device_slots.xml.dvpl`); @@ -636,7 +661,7 @@ export async function definitions() { /"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g, )) { const id = Number(match[1]); - const name = strings[match[2]]; + const name = getString(match[2]); gameModeNativeNames[match[3]] = id; gameDefinitions.gameModes[id] = { @@ -657,10 +682,10 @@ export async function definitions() { camouflagesXmlEntries.forEach(([camoKey, camo]) => { const yamlEntry = camouflagesYaml[camoKey]; const fullName = yamlEntry.userString - ? strings[yamlEntry.userString] + ? getString(yamlEntry.userString) : undefined; const shortName = yamlEntry.shortUserString - ? strings[yamlEntry.shortUserString] + ? getString(yamlEntry.shortUserString) : undefined; const resolvedTankName = shortName ?? fullName; const resolvedTankNameFull = @@ -668,7 +693,7 @@ export async function definitions() { camouflageDefinitions.camouflages[camo.id] = { id: camo.id, - name: strings[camo.userString], + name: getString(camo.userString), tank_name: resolvedTankName, tank_name_full: resolvedTankNameFull, }; @@ -866,8 +891,6 @@ export async function definitions() { id: tankId, roles: {}, camouflages: camouflages, - description: - wargamingTankopedia.data[tankId]?.description ?? undefined, fixed_camouflage: fixedCamouflage, crew, weight: tankDefinition.root.hull.weight, @@ -880,9 +903,9 @@ export async function definitions() { max_provisions: tankDefinition.root.provisionSlots, name: (tank.shortUserString - ? strings[tank.shortUserString] - : undefined) ?? strings[tank.userString], - name_full: strings[tank.userString], + ? getString(tank.shortUserString) + : undefined) ?? getString(tank.userString), + name_full: getString(tank.userString), nation, type: tankTags.includes('collectible') ? TankType.COLLECTOR @@ -959,7 +982,7 @@ export async function definitions() { tankDefinitions.tanks[tankId].tracks.push({ id: trackId, weight: track.weight, - name: strings[track.userString], + name: getString(track.userString), traverse_speed: track.rotationSpeed, dispersion_move: track.shotDispersionFactors.vehicleMovement, dispersion_traverse: track.shotDispersionFactors.vehicleRotation, @@ -987,7 +1010,7 @@ export async function definitions() { totalUnlocks.push(engine.unlocks); tankDefinitions.tanks[tankId].engines.push({ id: engineId, - name: strings[engineListEntry.userString], + name: getString(engineListEntry.userString), fire_chance: engineListEntry.fireStartingChance, tier: engineListEntry.level, weight: engineListEntry.weight, @@ -1041,14 +1064,12 @@ export async function definitions() { } }); + turret.userString; + tankDefinitions.tanks[tankId].turrets.push({ id: turretId, traverse_speed: turret.rotationSpeed, - name: - strings[turret.userString] ?? - turretKey - .replaceAll('_', ' ') - .replace(/^(Turret ([0-9] )?)+/, ''), + name: getString(turret.userString), tier: turret.level, guns: [], health: turret.maxHealth, @@ -1100,8 +1121,7 @@ export async function definitions() { const gunModel = Number( parsePath(gun.models.undamaged).name.split('_')[1], ); - const gunName = - strings[gunListEntry.userString] ?? gunKey.replaceAll('_', ' '); + const gunName = getString(gunListEntry.userString); const gunType = 'clip' in gun ? gun.pumpGunMode @@ -1250,8 +1270,7 @@ export async function definitions() { const gunShellEntry = gunListEntry.shots[shellKey]; const shell = shellList.root[shellKey]; const shellId = toUniqueId(nation, shell.id); - const shellName = - strings[shell.userString] ?? shellKey.replaceAll('_', ' '); + const shellName = getString(shell.userString); const penetrationRaw = gunShellEntry.piercingPower .split(' ') .filter((penetrationString) => penetrationString !== '') @@ -1385,8 +1404,8 @@ export async function definitions() { if (optionalDeviceKey === 'nextAvailableId') return; equipmentDefinitions.equipments[optionalDeviceEntry.id] = { - name: strings[optionalDeviceEntry.userString], - description: strings[optionalDeviceEntry.description], + name: getString(optionalDeviceEntry.userString), + description: getString(optionalDeviceEntry.description), }; }, ); @@ -1418,10 +1437,7 @@ export async function definitions() { game_mode_exclusive: 'gameModeFilter' in consumable, cooldown: consumable.script.cooldown, duration: consumable.script.duration, - name: - strings[consumable.userString] ?? - missingStrings[consumable.userString] ?? - `Unknown ${consumable.id}`, + name: getString(consumable.userString), exclude: [], include: [], }; @@ -1523,10 +1539,7 @@ export async function definitions() { exclude: [], include: [], game_mode_exclusive: 'gameModeFilter' in provision, - name: - strings[provision.userString] ?? - missingStrings[provision.userString] ?? - `Unknown ${provision.id}`, + name: getString(provision.userString), }; provisionDefinitions.provisions[provision.id] = entry; @@ -1650,10 +1663,12 @@ export async function definitions() { Object.entries(maps.maps).forEach(([key, map]) => { mapDefinitions.maps[map.id] = { id: map.id, - name: strings[`#maps:${key}:${map.localName}`], + name: getString(`#maps:${key}:${map.localName}`), }; }); + return; + await commitAssets('definitions', [ { content: encodePBBuffer(GameDefinitions, gameDefinitions), diff --git a/packages/website/package.json b/packages/website/package.json index 285a772b9..a49292006 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@blitzkit/core": "workspace:*", - "prate": "workspace:*", + "@blitzkit/i18n": "workspace:*", "@nanostores/persistent": "^0.10.2", "@nanostores/react": "^0.7.3", "@nivo/bar": "^0.87.0", diff --git a/packages/website/src/components/Tankopedia/HistorySection.tsx b/packages/website/src/components/Tankopedia/HistorySection.tsx deleted file mode 100644 index c77c15a86..000000000 --- a/packages/website/src/components/Tankopedia/HistorySection.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Heading, Text } from '@radix-ui/themes'; -import { Duel } from '../../stores/duel'; -import { PageWrapper } from '../PageWrapper'; - -export function HistorySection() { - const tank = Duel.use((state) => state.protagonist.tank); - - if (!tank.description) return null; - - return ( - - History of the {tank.name} - {tank.description} - - ); -} diff --git a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/_index.tsx b/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/_index.tsx index b98554025..024cf5eb9 100644 --- a/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/_index.tsx +++ b/packages/website/src/pages/[...locale]/tools/tankopedia/[id]/_index.tsx @@ -55,7 +55,6 @@ export function Page({ id, skeleton, locale }: PageProps) { -