From fccc266fefbbd02b47c0c5457e63023b4d35a595 Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Sun, 13 Sep 2026 11:54:41 -0700 Subject: [PATCH] Change name tables to key by name instead of ID - Also properly load modded text descriptions - Also remove dex.text.whateverName in favor of TL.whatever Client-side counterpart to https://github.com/smogon/pokemon-showdown/commit/92f01f8f71137ff926f8e63b488247c9ab8704cd --- build-tools/build-indexes | 27 +++------- build-tools/build-translations | 24 ++++++++- .../src/battle-dex-search.ts | 4 +- play.pokemonshowdown.com/src/battle-dex.ts | 54 ++++++------------- .../src/battle-searchresults.tsx | 18 +++---- .../src/battle-team-editor.tsx | 10 ++-- .../src/battle-tooltips.ts | 4 +- .../src/oldclient/search.js | 15 ++++-- play.pokemonshowdown.com/src/panels.tsx | 14 ++--- 9 files changed, 79 insertions(+), 91 deletions(-) diff --git a/build-tools/build-indexes b/build-tools/build-indexes index e40dd3d4d..ae116b5f2 100755 --- a/build-tools/build-indexes +++ b/build-tools/build-indexes @@ -27,6 +27,7 @@ child_process.execSync('npm run build', { cwd: 'caches/pokemon-showdown' }); console.log("DONE"); const Dex = require('../caches/pokemon-showdown/dist/sim/dex').Dex; +const { CLIENT_MODS } = require('./build-translations'); const CompoundWordNames = require('../caches/pokemon-showdown/dist/data/aliases').CompoundWordNames; const toID = Dex.toID; process.stdout.write("Loading gen 6 data... "); @@ -1213,11 +1214,12 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); const overrideSpeciesKeys = [ 'abilities', 'baseStats', 'cosmeticFormes', 'isNonstandard', 'requiredItems', 'types', 'unreleasedHidden', ]; + // Descs omitted (they're all compiled into data/text/*) const overrideMoveKeys = [ - 'accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'pp', 'priority', 'shortDesc', 'target', 'type', + 'accuracy', 'basePower', 'category', 'flags', 'isNonstandard', 'pp', 'priority', 'target', 'type', ]; - const overrideAbilityKeys = ['desc', 'flags', 'isNonstandard', 'rating', 'shortDesc']; - const overrideItemKeys = ['desc', 'fling', 'isNonstandard', 'naturalGift', 'shortDesc']; + const overrideAbilityKeys = ['flags', 'isNonstandard', 'rating']; + const overrideItemKeys = ['fling', 'isNonstandard', 'naturalGift']; // // Past gen table @@ -1306,9 +1308,7 @@ process.stdout.write("Building `data/teambuilder-tables.js`... "); // Mods // - for (const mod of [ - 'gen3rs', 'gen3frlg', 'gen5bw1', 'gen7letsgo', 'gen8bdsp', 'gen9ssb', 'champions', - ]) { + for (const mod of CLIENT_MODS) { const modDex = Dex.mod(mod); const modData = modDex.data; const parentDex = Dex.forGen(modDex.gen); @@ -1409,9 +1409,6 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j const Moves = requireNoCache('../caches/pokemon-showdown/dist/data/moves.js').Moves; for (const id in Moves) { const move = Dex.moves.get(Moves[id].name); - const text = Dex.text.get(move); - if (text.desc) Moves[id].desc = text.desc; - if (text.shortDesc) Moves[id].shortDesc = text.shortDesc; if (move.basePowerCallback) Moves[id].basePowerCallback = true; } const buf = 'exports.BattleMovedex = ' + es3stringify(Moves) + ';'; @@ -1425,12 +1422,6 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j { const Items = requireNoCache('../caches/pokemon-showdown/dist/data/items.js').Items; - for (const id in Items) { - const item = Dex.items.get(Items[id].name); - const text = Dex.text.get(item); - if (text.desc) Items[id].desc = text.desc; - if (text.shortDesc) Items[id].shortDesc = text.shortDesc; - } const buf = 'exports.BattleItems = ' + es3stringify(Items) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/items.js', buf); } @@ -1441,12 +1432,6 @@ process.stdout.write("Building `data/moves,items,abilities,typechart,learnsets.j { const Abilities = requireNoCache('../caches/pokemon-showdown/dist/data/abilities.js').Abilities; - for (const id in Abilities) { - const ability = Dex.abilities.get(Abilities[id].name); - const text = Dex.text.get(ability); - if (text.desc) Abilities[id].desc = text.desc; - if (text.shortDesc) Abilities[id].shortDesc = text.shortDesc; - } const buf = 'exports.BattleAbilities = ' + es3stringify(Abilities) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/abilities.js', buf); } diff --git a/build-tools/build-translations b/build-tools/build-translations index 5dd1f6a01..e84d70c63 100644 --- a/build-tools/build-translations +++ b/build-tools/build-translations @@ -12,6 +12,9 @@ const TEXT_DATA_PATH = path.resolve(ROOT_PATH, 'caches/pokemon-showdown/data/tex const UI_TRANSLATIONS_PATH = path.resolve(ROOT_PATH, 'translations'); const OUTPUT_PATH = path.resolve(ROOT_PATH, 'play.pokemonshowdown.com/data/text'); +/** Mods the client ships data for (see build-indexes) */ +const CLIENT_MODS = ['gen3rs', 'gen3frlg', 'gen5bw1', 'gen7letsgo', 'gen8bdsp', 'gen9ssb', 'champions']; + function es3stringify(obj) { const buf = JSON.stringify(obj); return buf.replace(/"([A-Za-z][A-Za-z0-9]*)":/g, (fullMatch, key) => ( @@ -32,12 +35,30 @@ function getLanguages() { return [...languages]; } +function addModText(Dex, text) { + for (const mod of CLIENT_MODS) { + const modData = Dex.mod(mod).data; + for (const table of ['Moves', 'Abilities', 'Items']) { + for (const id in modData[table]) { + const { desc, shortDesc } = modData[table][id]; + if (!desc && !shortDesc) continue; + text[table][id] = { + ...text[table][id], + [mod]: { desc: desc || shortDesc, shortDesc: shortDesc || desc }, + }; + } + } + } + return text; +} + function buildTextFiles(Dex, selectedLanguages = null, options = {}) { const uiCalls = updateTranslationFiles(options); const languages = selectedLanguages || getLanguages(); fs.mkdirSync(OUTPUT_PATH, { recursive: true }); for (const lang of languages) { - const battleTranslations = Dex.loadTextData(lang); + let battleTranslations = Dex.loadTextData(lang); + if (lang === 'en') battleTranslations = addModText(Dex, JSON.parse(JSON.stringify(battleTranslations))); const uiText = compileBattleUIText(loadTranslations(lang), uiCalls); let output = 'exports.BattleText = exports.BattleText || {};\n' + `exports.BattleText[${JSON.stringify(lang)}] = ${es3stringify(battleTranslations)};`; @@ -50,6 +71,7 @@ function buildTextFiles(Dex, selectedLanguages = null, options = {}) { } exports.buildTextFiles = buildTextFiles; +exports.CLIENT_MODS = CLIENT_MODS; if (require.main === module) { const Dex = require('../caches/pokemon-showdown/dist/sim/dex').Dex; diff --git a/play.pokemonshowdown.com/src/battle-dex-search.ts b/play.pokemonshowdown.com/src/battle-dex-search.ts index e3806460c..fe228d27b 100644 --- a/play.pokemonshowdown.com/src/battle-dex-search.ts +++ b/play.pokemonshowdown.com/src/battle-dex-search.ts @@ -577,7 +577,7 @@ export class DexSearch { switch (fType) { case 'type': let type = fId.charAt(0).toUpperCase() + fId.slice(1) as Dex.TypeName; - const typeName = TL.type[fId] || type; + const typeName = TL.type[type] || type; buf.push(['header', TL`${typeName}-type Pokémon`]); for (let id in BattlePokedex) { if (!BattlePokedex[id].types) continue; @@ -602,7 +602,7 @@ export class DexSearch { switch (fType) { case 'type': let type = fId.charAt(0).toUpperCase() + fId.slice(1); - const typeName = TL.type[fId] || type; + const typeName = TL.type[type] || type; buf.push(['header', TL`${typeName}-type moves`]); for (let id in BattleMovedex) { if (BattleMovedex[id].type === type) { diff --git a/play.pokemonshowdown.com/src/battle-dex.ts b/play.pokemonshowdown.com/src/battle-dex.ts index 150ad2912..abe1ff147 100644 --- a/play.pokemonshowdown.com/src/battle-dex.ts +++ b/play.pokemonshowdown.com/src/battle-dex.ts @@ -137,12 +137,6 @@ interface ClientDexText { languages(): Language[]; findLanguage(lang: string): Language | null; get(effect: TranslatableEffect, lang?: string): ClientEffectTextEntry; - typeName(name: string, lang?: string): string; - natureName(name: string, lang?: string): string; - categoryName(name: string, lang?: string): string; - genderName(name: string, lang?: string): string; - eggGroupName(name: string, lang?: string): string; - colorName(name: string, lang?: string): string; } function translate(strings: TemplateStringsArray, ...values: unknown[]): string; @@ -270,17 +264,13 @@ type OtherNameTable = 'StatNames' | 'StatMediumNames' | 'StatShortNames'; function getOtherName(table: OtherNameTable, name: string, lang: string): string { - let id: string = toID(name); - if (table === 'GenderNames') { - id = ({ m: 'male', f: 'female', n: 'genderless' } as Record)[id] || id; - } - return BattleText[lang]?.[table]?.[id] || BattleText.en?.[table]?.[id] || name; + return BattleText[lang]?.[table]?.[name] || BattleText.en?.[table]?.[name] || name; } /** * Does actually match ClientEffectTextEntry exactly. */ -function getTextEntry(effect: TranslatableEffect, gen: number, lang: string): ClientEffectTextEntry { +function getTextEntry(effect: TranslatableEffect, modid: string, gen: number, lang: string): ClientEffectTextEntry { if (effect.effectType === 'Species') { const entry = BattleText[lang]?.Pokedex?.[effect.id] || BattleText.en?.Pokedex?.[effect.id]; return { @@ -301,7 +291,7 @@ function getTextEntry(effect: TranslatableEffect, gen: number, lang: string): Cl const entry = {} as ClientEffectTextEntry; assignTextFields(entry, english); assignTextFields(entry, localized); - for (let i = 1; i <= 8; i++) { + for (let i = 1; i < Dex.gen; i++) { const genName = `gen${i}`; const englishGen = english[genName]; const localizedGen = localized[genName]; @@ -312,22 +302,22 @@ function getTextEntry(effect: TranslatableEffect, gen: number, lang: string): Cl entry[genName] = genEntry; } } - for (let i = 8; i >= gen; i--) { + for (let i = Dex.gen - 1; i >= gen; i--) { const genName = `gen${i}`; const englishGen = english[genName]; const localizedGen = localized[genName]; if (englishGen && typeof englishGen === 'object') assignTextFields(entry, englishGen); if (localizedGen && typeof localizedGen === 'object') assignTextFields(entry, localizedGen); } - const fallback = effect as unknown as { desc?: string, shortDesc?: string }; + if (!/^gen\d+$/.test(modid)) { + const englishMod = english[modid]; + const localizedMod = localized[modid]; + if (englishMod && typeof englishMod === 'object') assignTextFields(entry, englishMod); + if (localizedMod && typeof localizedMod === 'object') assignTextFields(entry, localizedMod); + } if (typeof entry.name !== 'string') entry.name = effect.name; - if (typeof entry.desc !== 'string') { - entry.desc = fallback.desc || fallback.shortDesc || - (typeof entry.shortDesc === 'string' ? entry.shortDesc : ''); - } - if (typeof entry.shortDesc !== 'string') { - entry.shortDesc = fallback.shortDesc || fallback.desc || entry.desc; - } + if (typeof entry.desc !== 'string') entry.desc = typeof entry.shortDesc === 'string' ? entry.shortDesc : ''; + if (typeof entry.shortDesc !== 'string') entry.shortDesc = entry.desc; return entry; } @@ -619,15 +609,8 @@ export const Dex = new class implements ModdedDex { return TEXT_LANGUAGE_TABLE[lang.toLowerCase()] || TEXT_LANGUAGE_TABLE[toID(lang)] || null; }, get: (effect: TranslatableEffect, lang = Dex.text.getLanguage()) => { - return getTextEntry(effect, 9, lang); + return getTextEntry(effect, 'gen9', 9, lang); }, - typeName: (name, lang = Dex.text.getLanguage()) => getOtherName('TypeNames', name, lang), - natureName: (name, lang = Dex.text.getLanguage()) => getOtherName('NatureNames', name, lang), - categoryName: (name, lang = Dex.text.getLanguage()) => - BattleText[lang]?.Tags?.[toID(name)]?.name || BattleText.en?.Tags?.[toID(name)]?.name || name, - genderName: (name, lang = Dex.text.getLanguage()) => getOtherName('GenderNames', name, lang), - eggGroupName: (name, lang = Dex.text.getLanguage()) => getOtherName('EggGroupNames', name, lang), - colorName: (name, lang = Dex.text.getLanguage()) => getOtherName('ColorNames', name, lang), }; getShortName(name: string) { @@ -1252,7 +1235,7 @@ export const Dex = new class implements ModdedDex { type = this.types.get(type).name; if (!type) type = '???'; let sanitizedType = type.replace(/\?/g, '%3f'); - const alt = BattleLog.escapeHTML(TL.type[toID(type)] || type); + const alt = BattleLog.escapeHTML(TL.type[type] || type); return `${alt}`; } @@ -1309,15 +1292,8 @@ export class ModdedDex { languages: () => Dex.text.languages(), findLanguage: lang => Dex.text.findLanguage(lang), get: (effect: TranslatableEffect, lang = Dex.text.getLanguage()) => { - return getTextEntry(effect, this.gen, lang); + return getTextEntry(effect, this.modid, this.gen, lang); }, - typeName: (name, lang = Dex.text.getLanguage()) => getOtherName('TypeNames', name, lang), - natureName: (name, lang = Dex.text.getLanguage()) => getOtherName('NatureNames', name, lang), - categoryName: (name, lang = Dex.text.getLanguage()) => - BattleText[lang]?.Tags?.[toID(name)]?.name || BattleText.en?.Tags?.[toID(name)]?.name || name, - genderName: (name, lang = Dex.text.getLanguage()) => getOtherName('GenderNames', name, lang), - eggGroupName: (name, lang = Dex.text.getLanguage()) => getOtherName('EggGroupNames', name, lang), - colorName: (name, lang = Dex.text.getLanguage()) => getOtherName('ColorNames', name, lang), }; moves = { get: (name: string): Move => { diff --git a/play.pokemonshowdown.com/src/battle-searchresults.tsx b/play.pokemonshowdown.com/src/battle-searchresults.tsx index 056938ee6..0e4f4367a 100644 --- a/play.pokemonshowdown.com/src/battle-searchresults.tsx +++ b/play.pokemonshowdown.com/src/battle-searchresults.tsx @@ -33,9 +33,9 @@ function getSearchDisplayName(search: DexSearch, type: string, name: string): st case 'move': return search.dex.text.get(search.dex.moves.get(name)).name; case 'item': return search.dex.text.get(search.dex.items.get(name)).name; case 'ability': return search.dex.text.get(search.dex.abilities.get(name)).name; - case 'type': return search.dex.text.typeName(name); - case 'category': return search.dex.text.categoryName(name); - case 'egggroup': return search.dex.text.eggGroupName(name); + case 'type': return TL.type[name] || name; + case 'category': return TL.tag[toID(name)] || name; + case 'egggroup': return TL.egggroup[name] || name; default: return name; } } @@ -127,7 +127,7 @@ export class PSSearchResults extends preact.Component<{ if (errorMessage) return `${buf}${errorMessage}`; buf += `${pokemon.types.map(type => - `${escapeHTML(search.dex.text.typeName(type))}` + `${escapeHTML(TL.type[type] || type)}` ).join('')}`; if (search.numAbilityCols) { @@ -269,9 +269,9 @@ export class PSSearchResults extends preact.Component<{ } buf += `` + `` + + `alt="${escapeHTML(TL.type[move.type] || move.type)}" height="14" width="32" class="pixelated" />` + `` + + `alt="${escapeHTML(TL.tag[toID(move.category)] || move.category)}" height="14" width="32" class="pixelated" />` + `` + `${move.category !== 'Status' ? `Power
${move.basePower || '—'}` : ''}
` + `Accuracy
` + @@ -283,7 +283,7 @@ export class PSSearchResults extends preact.Component<{ renderTypeRowHTML(index: number, id: ID, matchStart: number, matchEnd: number, errorMessage?: string) { const name = id.charAt(0).toUpperCase() + id.slice(1); - const displayName = this.props.search.dex.text.typeName(name); + const displayName = TL.type[name] || name; [matchStart, matchEnd] = getLocalizedMatch(displayName, name, matchStart, matchEnd); return `
  • ( - {editor.dex.text.typeName(counter.type)} + {TL.type[counter.type] || counter.type} {counter.resists} {TL`Resist`} {counter.weaknesses} {TL`Weak`} @@ -1802,7 +1802,7 @@ class TeamTextbox extends preact.Component<{ const genderID = (set.gender || species.gender || 'N') as Dex.GenderName; // em dash - const gender = genderID === 'N' ? '\u2014' : editor.dex.text.genderName(genderID); + const gender = genderID === 'N' ? '\u2014' : TL.gender[genderID] || genderID; return
  • '; @@ -522,7 +527,7 @@ return buf; } - buf += '' + BattleLog.escapeHTML(ability.shortDesc) + ' '; + buf += '' + BattleLog.escapeHTML(this.dex().text.get(ability).shortDesc) + ' '; buf += ''; @@ -579,7 +584,7 @@ buf += 'PP
    ' + pp + '
    '; // desc - buf += '' + BattleLog.escapeHTML(move.shortDesc) + ' '; + buf += '' + BattleLog.escapeHTML(this.dex().text.get(move).shortDesc) + ' '; buf += ''; @@ -620,7 +625,7 @@ buf += 'PP
    ' + pp + '
    '; // desc - buf += '' + BattleLog.escapeHTML(move.shortDesc || move.desc) + ' '; + buf += '' + BattleLog.escapeHTML(this.dex().text.get(move).shortDesc) + ' '; buf += ''; @@ -663,7 +668,7 @@ buf += 'PP
    ' + pp + '
    '; // desc - buf += '' + BattleLog.escapeHTML(move.shortDesc || move.desc) + ' '; + buf += '' + BattleLog.escapeHTML(this.dex().text.get(move).shortDesc) + ' '; buf += ''; diff --git a/play.pokemonshowdown.com/src/panels.tsx b/play.pokemonshowdown.com/src/panels.tsx index 05ca55532..c42d0113f 100644 --- a/play.pokemonshowdown.com/src/panels.tsx +++ b/play.pokemonshowdown.com/src/panels.tsx @@ -1731,33 +1731,33 @@ export function PSIcon( } const sanitizedType = typeName.replace(/\?/g, '%3f'); return {Dex.text.typeName(typeName)}; } if ('category' in props) { const categoryID = toID(props.category); - let sanitizedCategory = ''; + let categoryName = ''; switch (categoryID) { case 'physical': case 'special': case 'status': - sanitizedCategory = categoryID.charAt(0).toUpperCase() + categoryID.slice(1); + categoryName = categoryID.charAt(0).toUpperCase() + categoryID.slice(1); break; default: - sanitizedCategory = 'undefined'; + categoryName = 'undefined'; break; } return {Dex.text.categoryName(sanitizedCategory)}; } if ('gender' in props) { return {Dex.text.genderName(props.gender)}; } return null!;