From 7cb39bd539bde40e7a4eab62a75bc31683235f54 Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 9 Oct 2024 03:52:44 -0400 Subject: [PATCH 1/5] create and use namespace-i18n-plugin.ts --- src/plugins/i18n.ts | 92 +--------------------- src/plugins/vite/namespaces-i18n-plugin.ts | 85 ++++++++++++++++++++ vite.config.ts | 4 +- 3 files changed, 91 insertions(+), 90 deletions(-) create mode 100644 src/plugins/vite/namespaces-i18n-plugin.ts diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index be4c6983c0a..b5afb073f27 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -116,6 +116,8 @@ function i18nMoneyFormatter(amount: any): string { return `@[MONEY]{${i18next.t("common:money", { amount })}}`; } +const nsEn = []; + //#region Exports /** @@ -166,95 +168,7 @@ export async function initI18n(): Promise { }, }, defaultNS: "menu", - ns: [ - "ability", - "abilityTriggers", - "arenaFlyout", - "arenaTag", - "battle", - "battleScene", - "battleInfo", - "battleMessageUiHandler", - "battlePokemonForm", - "battlerTags", - "berry", - "bgmName", - "biome", - "challenges", - "commandUiHandler", - "common", - "achv", - "dialogue", - "battleSpecDialogue", - "miscDialogue", - "doubleBattleDialogue", - "egg", - "fightUiHandler", - "filterBar", - "gameMode", - "gameStatsUiHandler", - "growth", - "menu", - "menuUiHandler", - "modifier", - "modifierType", - "move", - "nature", - "pokeball", - "pokemon", - "pokemonForm", - "pokemonInfo", - "pokemonInfoContainer", - "pokemonSummary", - "saveSlotSelectUiHandler", - "settings", - "splashMessages", - "starterSelectUiHandler", - "statusEffect", - "terrain", - "titles", - "trainerClasses", - "trainerNames", - "tutorial", - "voucher", - "weather", - "partyUiHandler", - "modifierSelectUiHandler", - "moveTriggers", - "runHistory", - "mysteryEncounters/mysteriousChallengers", - "mysteryEncounters/mysteriousChest", - "mysteryEncounters/darkDeal", - "mysteryEncounters/fightOrFlight", - "mysteryEncounters/slumberingSnorlax", - "mysteryEncounters/trainingSession", - "mysteryEncounters/departmentStoreSale", - "mysteryEncounters/shadyVitaminDealer", - "mysteryEncounters/fieldTrip", - "mysteryEncounters/safariZone", - "mysteryEncounters/lostAtSea", - "mysteryEncounters/fieryFallout", - "mysteryEncounters/theStrongStuff", - "mysteryEncounters/thePokemonSalesman", - "mysteryEncounters/anOfferYouCantRefuse", - "mysteryEncounters/delibirdy", - "mysteryEncounters/absoluteAvarice", - "mysteryEncounters/aTrainersTest", - "mysteryEncounters/trashToTreasure", - "mysteryEncounters/berriesAbound", - "mysteryEncounters/clowningAround", - "mysteryEncounters/partTimer", - "mysteryEncounters/dancingLessons", - "mysteryEncounters/weirdDream", - "mysteryEncounters/theWinstrateChallenge", - "mysteryEncounters/teleportingHijinks", - "mysteryEncounters/bugTypeSuperfan", - "mysteryEncounters/funAndGames", - "mysteryEncounters/uncommonBreed", - "mysteryEncounters/globalTradeSystem", - "mysteryEncounters/theExpertPokemonBreeder", - "mysteryEncounterMessages", - ], + ns: nsEn, // assign with namespaces-i18n-plugin.ts detection: { lookupLocalStorage: "prLang" }, diff --git a/src/plugins/vite/namespaces-i18n-plugin.ts b/src/plugins/vite/namespaces-i18n-plugin.ts new file mode 100644 index 00000000000..24e7734b94c --- /dev/null +++ b/src/plugins/vite/namespaces-i18n-plugin.ts @@ -0,0 +1,85 @@ +import { normalizePath, type Plugin as VitePlugin } from "vite"; +import fs from "fs"; +import path from "path"; + +function kebabCaseToCamelCase(str: string): string { + return str.split("-").map((text, index) => { + if (index > 0) { + return text.split("").map((char, i) => i === 0 ? char.toUpperCase() : char).join(""); + } + return text; + }).join(""); +} + +function getNameSpaces(dir: string) { + const namespace: string[] = []; + const files = fs.readdirSync(dir); + + for (const file of files) { + const filePath = path.join(dir, file); + const stat = fs.lstatSync(filePath); + + if (stat.isDirectory()) { + const subnamespace = getNameSpaces(filePath); + for (let i = 0; i < subnamespace.length; i++) { + let ns = subnamespace[i]; + if (kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) { + ns = subnamespace[i].replace(/Dialogue$/, ""); + } + namespace.push(`${kebabCaseToCamelCase(file).replace(".json", "")}/${ns}`); + } + } else if (path.extname(file) === ".json") { + namespace.push(kebabCaseToCamelCase(file).replace(".json", "")); + } + } + + return namespace; +} + +function isFileInsideDir(file, dir) { + const filePath = path.normalize(file); + const dirPath = path.normalize(dir); + return filePath.startsWith(dirPath); +} + +export function LocaleNamespace(): VitePlugin { + const nsLocation = "./public/locales"; + const nsEn = `${nsLocation}/en`; // Default namespace + let namespaces = getNameSpaces(nsEn); + // const nsEnRegex = new RegExp(`^${nsEn.replace(/\//g, "\\/")}.*\\.json$`); + const nsAbsolutePath = path.resolve(process.cwd(), nsLocation); // Convert to absolute path + + return { + name: "namespaces-i18next", + buildStart() { + if (process.env.NODE_ENV === "production") { + console.log("Assign namespace to constant nsEn"); + } + }, + configureServer(server) { + const restartHandler = async (file, action: string) => { + if (isFileInsideDir(file, nsAbsolutePath) && file.endsWith(".json")) { + console.log(`\x1b[34m${normalizePath(file.replace(nsAbsolutePath, ""))}\x1b[0m ${action}, reloading page...`); + + namespaces = await getNameSpaces(nsEn); + await server.moduleGraph.invalidateAll(); + await server.ws.send({ + type: "full-reload", + }); + } + }; + server.watcher.on("change", (file) => restartHandler(file, "updated")); + server.watcher.on("add", (file) => restartHandler(file, "added")); + server.watcher.on("unlink", (file) => restartHandler(file, "removed")); + }, + transform: { + handler(code, id) { + if (id.endsWith("i18n.ts")) { + return code.replace("const nsEn = [];", `const nsEn = ${JSON.stringify(namespaces)};`); + } + return code; + }, + }, + + }; +} diff --git a/vite.config.ts b/vite.config.ts index 946315c4b7b..2888f1ed510 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,11 +1,13 @@ import { defineConfig, loadEnv, Rollup, UserConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; import { minifyJsonPlugin } from "./src/plugins/vite/vite-minify-json-plugin"; +import { LocaleNamespace } from "./src/plugins/vite/namespaces-i18n-plugin"; export const defaultConfig: UserConfig = { plugins: [ tsconfigPaths(), - minifyJsonPlugin(["images", "battle-anims"], true) + minifyJsonPlugin(["images", "battle-anims"], true), + LocaleNamespace() ], clearScreen: false, appType: "mpa", From 304785a5530cbfe8802af67013b44237df2ff17f Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 9 Oct 2024 15:18:34 -0400 Subject: [PATCH 2/5] Changes to src/utils.ts to ensure correct importing by Vite plugins and extraction of the amespaceMap constant to its own file. --- src/plugins/i18n.ts | 13 +----- src/plugins/namespacemap.ts | 14 +++++++ src/plugins/vite/namespaces-i18n-plugin.ts | 21 +++++----- src/utils.ts | 46 +++++++++++++++++----- 4 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 src/plugins/namespacemap.ts diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index e488b62af45..25e56e05e1e 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -4,6 +4,7 @@ import LanguageDetector from "i18next-browser-languagedetector"; import HttpBackend from "i18next-http-backend"; import processor, { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; import pkg from "../../package.json"; +import { namespaceMap } from "./namespacemap"; //#region Interfaces/Types @@ -72,18 +73,6 @@ const fonts: Array = [ }, ]; -/** maps namespaces that deviate from the file-name */ -const namespaceMap = { - titles: "trainer-titles", - moveTriggers: "move-trigger", - abilityTriggers: "ability-trigger", - battlePokemonForm: "pokemon-form-battle", - miscDialogue: "dialogue-misc", - battleSpecDialogue: "dialogue-final-boss", - doubleBattleDialogue: "dialogue-double-battle", - splashMessages: "splash-texts", - mysteryEncounterMessages: "mystery-encounter-texts", -}; //#region Functions diff --git a/src/plugins/namespacemap.ts b/src/plugins/namespacemap.ts new file mode 100644 index 00000000000..00e9b4dcda2 --- /dev/null +++ b/src/plugins/namespacemap.ts @@ -0,0 +1,14 @@ +// When changing the file, the server restarts because of the namespaces-18n-plugin.ts + +/** maps namespaces that deviate from the file-name */ +export const namespaceMap = { + titles: "trainer-titles", + moveTriggers: "move-trigger", + abilityTriggers: "ability-trigger", + battlePokemonForm: "pokemon-form-battle", + miscDialogue: "dialogue-misc", + battleSpecDialogue: "dialogue-final-boss", + doubleBattleDialogue: "dialogue-double-battle", + splashMessages: "splash-texts", + mysteryEncounterMessages: "mystery-encounter-texts", +}; diff --git a/src/plugins/vite/namespaces-i18n-plugin.ts b/src/plugins/vite/namespaces-i18n-plugin.ts index 24e7734b94c..17de7c55c06 100644 --- a/src/plugins/vite/namespaces-i18n-plugin.ts +++ b/src/plugins/vite/namespaces-i18n-plugin.ts @@ -1,15 +1,10 @@ import { normalizePath, type Plugin as VitePlugin } from "vite"; import fs from "fs"; import path from "path"; +import { namespaceMap } from "../namespacemap"; +import { kebabCaseToCamelCase, objectSwap } from "../../utils"; -function kebabCaseToCamelCase(str: string): string { - return str.split("-").map((text, index) => { - if (index > 0) { - return text.split("").map((char, i) => i === 0 ? char.toUpperCase() : char).join(""); - } - return text; - }).join(""); -} +const namespaceMapSwap = objectSwap(namespaceMap); function getNameSpaces(dir: string) { const namespace: string[] = []; @@ -23,13 +18,19 @@ function getNameSpaces(dir: string) { const subnamespace = getNameSpaces(filePath); for (let i = 0; i < subnamespace.length; i++) { let ns = subnamespace[i]; - if (kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) { + if (namespaceMapSwap[file.replace(".json", "")]) { + ns = namespaceMapSwap[file.replace(".json", "")]; + } else if (kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) { ns = subnamespace[i].replace(/Dialogue$/, ""); } namespace.push(`${kebabCaseToCamelCase(file).replace(".json", "")}/${ns}`); } } else if (path.extname(file) === ".json") { - namespace.push(kebabCaseToCamelCase(file).replace(".json", "")); + let ns = kebabCaseToCamelCase(file).replace(".json", ""); + if (namespaceMapSwap[file.replace(".json", "")]) { + ns = namespaceMapSwap[file.replace(".json", "")]; + } + namespace.push(ns); } } diff --git a/src/utils.ts b/src/utils.ts index 9cc95b00826..e50cac59c88 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,7 @@ -import { MoneyFormat } from "#enums/money-format"; -import { Moves } from "#enums/moves"; +// So that the utils.ts is also accessible to plugins/vite, it's important that the paths are relative and not aliases .. +// Although, due to this, any change made to the file will cause a server restart +import { MoneyFormat } from "./enums/money-format"; +import { Moves } from "./enums/moves"; import i18next from "i18next"; export type nil = null | undefined; @@ -270,23 +272,23 @@ export function executeIf(condition: boolean, promiseFunc: () => Promise): export const sessionIdKey = "pokerogue_sessionId"; // Check if the current hostname is 'localhost' or an IP address, and ensure a port is specified export const isLocal = ( - (window.location.hostname === "localhost" || - /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(window.location.hostname)) && - window.location.port !== "") || window.location.hostname === ""; + (globalThis.location?.hostname === "localhost" || + /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(globalThis.location?.hostname)) && + globalThis.location?.port !== "") || globalThis.location?.hostname === ""; -export const localServerUrl = import.meta.env.VITE_SERVER_URL ?? `http://${window.location.hostname}:${window.location.port + 1}`; +export const localServerUrl = import.meta.env?.VITE_SERVER_URL ?? `http://${globalThis.location?.hostname}:${globalThis.location?.port + 1}`; // Set the server URL based on whether it's local or not export const apiUrl = localServerUrl ?? "https://api.pokerogue.net"; // used to disable api calls when isLocal is true and a server is not found export let isLocalServerConnected = true; -export const isBeta = import.meta.env.MODE === "beta"; // this checks to see if the env mode is development. Technically this gives the same value for beta AND for dev envs +export const isBeta = import.meta.env?.MODE === "beta"; // this checks to see if the env mode is development. Technically this gives the same value for beta AND for dev envs export function setCookie(cName: string, cValue: string): void { const expiration = new Date(); expiration.setTime(new Date().getTime() + 3600000 * 24 * 30 * 3/*7*/); - document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Expires=${expiration.toUTCString()}`; + document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${globalThis.location.hostname};Path=/;Expires=${expiration.toUTCString()}`; } export function removeCookie(cName: string): void { @@ -294,7 +296,7 @@ export function removeCookie(cName: string): void { document.cookie = `${cName}=;Secure;SameSite=Strict;Domain=pokerogue.net;Path=/;Max-Age=-1`; // we need to remove the cookie from the main domain as well } - document.cookie = `${cName}=;Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Max-Age=-1`; + document.cookie = `${cName}=;Secure;SameSite=Strict;Domain=${globalThis.location.hostname};Path=/;Max-Age=-1`; document.cookie = `${cName}=;Secure;SameSite=Strict;Path=/;Max-Age=-1`; // legacy cookie without domain, for older cookies to prevent a login loop } @@ -651,3 +653,29 @@ export function animationFileName(move: Moves): string { export function camelCaseToKebabCase(str: string): string { return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase()); } + +/** + * Transforms a kebab-case string into a camelCase string + * @param str The kebabCase string + * @returns A camelCase string + * + * @source {@link https://stackoverflow.com/a/23013726} + */ +export function kebabCaseToCamelCase(str: string): string { + return str.replace(/-./g, (x)=> x[1].toUpperCase()); +} + +/** + * Swap the value with the key and the key with the value + * @param json type {[key: string]: string} + * @returns [value]: key + * + * @source {@link https://stackoverflow.com/a/23013726} + */ +export function objectSwap(json: {[key: string]: string}): {[value: string]: string} { + const ret = {}; + for (const key in json) { + ret[json[key]] = key; + } + return ret; +} From e63157717dc0e655593d21af7a93cabfa53eba5e Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 9 Oct 2024 15:43:36 -0400 Subject: [PATCH 3/5] Added more comments for create help a new namespace --- src/plugins/i18n.ts | 4 +++- src/plugins/namespacemap.ts | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 25e56e05e1e..9f82556c7ae 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -130,7 +130,9 @@ export async function initI18n(): Promise { * Don't forget to declare new language in `supportedLngs` i18next initializer * * Q: How do I add a new namespace? - * A: To add a new namespace, create a new file in each language folder with the translations. + * A: To add a new namespace, create a new file .json in each language folder with the translations. + * The expected format for the files is kebab-case {@link https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case} + * If you want the namespace name to be different from the file name, configure it in namespacemap.ts. * Then update the config file for that language in its locale directory * and the CustomTypeOptions interface in the @types/i18next.d.ts file. * diff --git a/src/plugins/namespacemap.ts b/src/plugins/namespacemap.ts index 00e9b4dcda2..cb16b0ee108 100644 --- a/src/plugins/namespacemap.ts +++ b/src/plugins/namespacemap.ts @@ -1,6 +1,9 @@ // When changing the file, the server restarts because of the namespaces-18n-plugin.ts -/** maps namespaces that deviate from the file-name */ +/** + * ### maps namespaces that deviate from the file-name + * @description expects file-name as value and custom-namespace as key + * */ export const namespaceMap = { titles: "trainer-titles", moveTriggers: "move-trigger", From 26772fa85122337ee8d8046b6af72b030bfd6d5e Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 11 Oct 2024 14:39:44 -0400 Subject: [PATCH 4/5] create utils-plugins.ts and more docs --- src/plugins/i18n.ts | 6 +-- src/plugins/namespacemap.ts | 17 -------- src/plugins/utils-plugins.ts | 49 +++++++++++++++++++++ src/plugins/vite/namespaces-i18n-plugin.ts | 50 ++++++++++++---------- src/utils.ts | 47 +++++--------------- 5 files changed, 90 insertions(+), 79 deletions(-) delete mode 100644 src/plugins/namespacemap.ts create mode 100644 src/plugins/utils-plugins.ts diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 9f82556c7ae..d870b505e82 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -4,7 +4,7 @@ import LanguageDetector from "i18next-browser-languagedetector"; import HttpBackend from "i18next-http-backend"; import processor, { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; import pkg from "../../package.json"; -import { namespaceMap } from "./namespacemap"; +import { namespaceMap } from "./utils-plugins"; //#region Interfaces/Types @@ -131,7 +131,7 @@ export async function initI18n(): Promise { * * Q: How do I add a new namespace? * A: To add a new namespace, create a new file .json in each language folder with the translations. - * The expected format for the files is kebab-case {@link https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case} + * The expected format for the file-name is kebab-case {@link https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case} * If you want the namespace name to be different from the file name, configure it in namespacemap.ts. * Then update the config file for that language in its locale directory * and the CustomTypeOptions interface in the @types/i18next.d.ts file. @@ -161,7 +161,7 @@ export async function initI18n(): Promise { }, }, defaultNS: "menu", - ns: nsEn, // assign with namespaces-i18n-plugin.ts + ns: nsEn, // assigned with #app/plugins/vite/namespaces-i18n-plugin.ts detection: { lookupLocalStorage: "prLang" }, diff --git a/src/plugins/namespacemap.ts b/src/plugins/namespacemap.ts deleted file mode 100644 index cb16b0ee108..00000000000 --- a/src/plugins/namespacemap.ts +++ /dev/null @@ -1,17 +0,0 @@ -// When changing the file, the server restarts because of the namespaces-18n-plugin.ts - -/** - * ### maps namespaces that deviate from the file-name - * @description expects file-name as value and custom-namespace as key - * */ -export const namespaceMap = { - titles: "trainer-titles", - moveTriggers: "move-trigger", - abilityTriggers: "ability-trigger", - battlePokemonForm: "pokemon-form-battle", - miscDialogue: "dialogue-misc", - battleSpecDialogue: "dialogue-final-boss", - doubleBattleDialogue: "dialogue-double-battle", - splashMessages: "splash-texts", - mysteryEncounterMessages: "mystery-encounter-texts", -}; diff --git a/src/plugins/utils-plugins.ts b/src/plugins/utils-plugins.ts new file mode 100644 index 00000000000..a20fa91ed5a --- /dev/null +++ b/src/plugins/utils-plugins.ts @@ -0,0 +1,49 @@ +import path from "path"; // vite externalize in production, see https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility + +/** + * ### maps namespaces that deviate from the file-name + * @description expects file-name as value and custom-namespace as key + * */ +export const namespaceMap = { + titles: "trainer-titles", + moveTriggers: "move-trigger", + abilityTriggers: "ability-trigger", + battlePokemonForm: "pokemon-form-battle", + miscDialogue: "dialogue-misc", + battleSpecDialogue: "dialogue-final-boss", + doubleBattleDialogue: "dialogue-double-battle", + splashMessages: "splash-texts", + mysteryEncounterMessages: "mystery-encounter-texts", +}; + +/** + * Transforms a kebab-case string into a camelCase string + * @param str The kebabCase string + * @returns A camelCase string + * + * @source {@link https://stackoverflow.com/a/23013726} + */ +export function kebabCaseToCamelCase(str: string): string { + return str.replace(/-./g, (x)=> x[1].toUpperCase()); +} + +/** + * Swap the value with the key and the key with the value + * @param json type {[key: string]: string} + * @returns [value]: key + * + * @source {@link https://stackoverflow.com/a/23013726} + */ +export function objectSwap(json: {[key: string]: string}): {[value: string]: string} { + const ret = {}; + for (const key in json) { + ret[json[key]] = key; + } + return ret; +} + +export function isFileInsideDir(file, dir) { + const filePath = path.normalize(file); + const dirPath = path.normalize(dir); + return filePath.startsWith(dirPath); +} diff --git a/src/plugins/vite/namespaces-i18n-plugin.ts b/src/plugins/vite/namespaces-i18n-plugin.ts index 17de7c55c06..35684e27934 100644 --- a/src/plugins/vite/namespaces-i18n-plugin.ts +++ b/src/plugins/vite/namespaces-i18n-plugin.ts @@ -1,12 +1,16 @@ import { normalizePath, type Plugin as VitePlugin } from "vite"; import fs from "fs"; import path from "path"; -import { namespaceMap } from "../namespacemap"; -import { kebabCaseToCamelCase, objectSwap } from "../../utils"; +import * as Utils from "../utils-plugins"; -const namespaceMapSwap = objectSwap(namespaceMap); - -function getNameSpaces(dir: string) { +const namespaceMapSwap = Utils.objectSwap(Utils.namespaceMap); +/** + * Crawl a directory recursively for json files to returns her name with camelCase format... + * Also if file is in directory returns format "dir/fileName" format + * @param dir the directory to crawl + * @returns {string[]} + */ +function getNameSpaces(dir: string): string[] { const namespace: string[] = []; const files = fs.readdirSync(dir); @@ -20,13 +24,13 @@ function getNameSpaces(dir: string) { let ns = subnamespace[i]; if (namespaceMapSwap[file.replace(".json", "")]) { ns = namespaceMapSwap[file.replace(".json", "")]; - } else if (kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) { + } else if (Utils.kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) { ns = subnamespace[i].replace(/Dialogue$/, ""); } - namespace.push(`${kebabCaseToCamelCase(file).replace(".json", "")}/${ns}`); + namespace.push(`${Utils.kebabCaseToCamelCase(file).replace(".json", "")}/${ns}`); } } else if (path.extname(file) === ".json") { - let ns = kebabCaseToCamelCase(file).replace(".json", ""); + let ns = Utils.kebabCaseToCamelCase(file).replace(".json", ""); if (namespaceMapSwap[file.replace(".json", "")]) { ns = namespaceMapSwap[file.replace(".json", "")]; } @@ -37,41 +41,41 @@ function getNameSpaces(dir: string) { return namespace; } -function isFileInsideDir(file, dir) { - const filePath = path.normalize(file); - const dirPath = path.normalize(dir); - return filePath.startsWith(dirPath); -} export function LocaleNamespace(): VitePlugin { - const nsLocation = "./public/locales"; - const nsEn = `${nsLocation}/en`; // Default namespace + const nsRelativePath = "./public/locales"; + const nsEn = `${nsRelativePath}/en`; // Default namespace let namespaces = getNameSpaces(nsEn); - // const nsEnRegex = new RegExp(`^${nsEn.replace(/\//g, "\\/")}.*\\.json$`); - const nsAbsolutePath = path.resolve(process.cwd(), nsLocation); // Convert to absolute path + const nsAbsolutePath = path.resolve(process.cwd(), nsRelativePath); return { name: "namespaces-i18next", buildStart() { if (process.env.NODE_ENV === "production") { - console.log("Assign namespace to constant nsEn"); + console.log("Collect namespaces"); } }, configureServer(server) { const restartHandler = async (file, action: string) => { - if (isFileInsideDir(file, nsAbsolutePath) && file.endsWith(".json")) { + /** + * If any JSON file in {@linkcode nsLocation} is created/modified.. + * refresh the page to update the namespaces of i18next + */ + if (Utils.isFileInsideDir(file, nsAbsolutePath) && file.endsWith(".json")) { console.log(`\x1b[34m${normalizePath(file.replace(nsAbsolutePath, ""))}\x1b[0m ${action}, reloading page...`); namespaces = await getNameSpaces(nsEn); await server.moduleGraph.invalidateAll(); await server.ws.send({ - type: "full-reload", + type: "full-reload" }); } }; - server.watcher.on("change", (file) => restartHandler(file, "updated")); - server.watcher.on("add", (file) => restartHandler(file, "added")); - server.watcher.on("unlink", (file) => restartHandler(file, "removed")); + + server.watcher + .on("change", (file) => restartHandler(file, "updated")) + .on("add", (file) => restartHandler(file, "added")) + .on("unlink", (file) => restartHandler(file, "removed")); }, transform: { handler(code, id) { diff --git a/src/utils.ts b/src/utils.ts index e50cac59c88..37b11ef2f08 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,7 @@ -// So that the utils.ts is also accessible to plugins/vite, it's important that the paths are relative and not aliases .. -// Although, due to this, any change made to the file will cause a server restart -import { MoneyFormat } from "./enums/money-format"; -import { Moves } from "./enums/moves"; +import { MoneyFormat } from "#enums/money-format"; +import { Moves } from "#enums/moves"; import i18next from "i18next"; +import * as plugins from "./plugins/utils-plugins"; export type nil = null | undefined; @@ -272,23 +271,23 @@ export function executeIf(condition: boolean, promiseFunc: () => Promise): export const sessionIdKey = "pokerogue_sessionId"; // Check if the current hostname is 'localhost' or an IP address, and ensure a port is specified export const isLocal = ( - (globalThis.location?.hostname === "localhost" || - /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(globalThis.location?.hostname)) && - globalThis.location?.port !== "") || globalThis.location?.hostname === ""; + (window.location.hostname === "localhost" || + /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(window.location.hostname)) && + window.location.port !== "") || window.location.hostname === ""; -export const localServerUrl = import.meta.env?.VITE_SERVER_URL ?? `http://${globalThis.location?.hostname}:${globalThis.location?.port + 1}`; +export const localServerUrl = import.meta.env.VITE_SERVER_URL ?? `http://${window.location.hostname}:${window.location.port + 1}`; // Set the server URL based on whether it's local or not export const apiUrl = localServerUrl ?? "https://api.pokerogue.net"; // used to disable api calls when isLocal is true and a server is not found export let isLocalServerConnected = true; -export const isBeta = import.meta.env?.MODE === "beta"; // this checks to see if the env mode is development. Technically this gives the same value for beta AND for dev envs +export const isBeta = import.meta.env.MODE === "beta"; // this checks to see if the env mode is development. Technically this gives the same value for beta AND for dev envs export function setCookie(cName: string, cValue: string): void { const expiration = new Date(); expiration.setTime(new Date().getTime() + 3600000 * 24 * 30 * 3/*7*/); - document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${globalThis.location.hostname};Path=/;Expires=${expiration.toUTCString()}`; + document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Expires=${expiration.toUTCString()}`; } export function removeCookie(cName: string): void { @@ -296,7 +295,7 @@ export function removeCookie(cName: string): void { document.cookie = `${cName}=;Secure;SameSite=Strict;Domain=pokerogue.net;Path=/;Max-Age=-1`; // we need to remove the cookie from the main domain as well } - document.cookie = `${cName}=;Secure;SameSite=Strict;Domain=${globalThis.location.hostname};Path=/;Max-Age=-1`; + document.cookie = `${cName}=;Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Max-Age=-1`; document.cookie = `${cName}=;Secure;SameSite=Strict;Path=/;Max-Age=-1`; // legacy cookie without domain, for older cookies to prevent a login loop } @@ -654,28 +653,4 @@ export function camelCaseToKebabCase(str: string): string { return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase()); } -/** - * Transforms a kebab-case string into a camelCase string - * @param str The kebabCase string - * @returns A camelCase string - * - * @source {@link https://stackoverflow.com/a/23013726} - */ -export function kebabCaseToCamelCase(str: string): string { - return str.replace(/-./g, (x)=> x[1].toUpperCase()); -} - -/** - * Swap the value with the key and the key with the value - * @param json type {[key: string]: string} - * @returns [value]: key - * - * @source {@link https://stackoverflow.com/a/23013726} - */ -export function objectSwap(json: {[key: string]: string}): {[value: string]: string} { - const ret = {}; - for (const key in json) { - ret[json[key]] = key; - } - return ret; -} +export { plugins }; From 40529649f432c30ad407e95f11a5ab98c95015f5 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 11 Oct 2024 14:46:08 -0400 Subject: [PATCH 5/5] console info appearance --- src/plugins/vite/namespaces-i18n-plugin.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/vite/namespaces-i18n-plugin.ts b/src/plugins/vite/namespaces-i18n-plugin.ts index 35684e27934..74e2ed805fa 100644 --- a/src/plugins/vite/namespaces-i18n-plugin.ts +++ b/src/plugins/vite/namespaces-i18n-plugin.ts @@ -27,6 +27,7 @@ function getNameSpaces(dir: string): string[] { } else if (Utils.kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) { ns = subnamespace[i].replace(/Dialogue$/, ""); } + // format "directory/namespace" for namespace in folder namespace.push(`${Utils.kebabCaseToCamelCase(file).replace(".json", "")}/${ns}`); } } else if (path.extname(file) === ".json") { @@ -62,7 +63,9 @@ export function LocaleNamespace(): VitePlugin { * refresh the page to update the namespaces of i18next */ if (Utils.isFileInsideDir(file, nsAbsolutePath) && file.endsWith(".json")) { - console.log(`\x1b[34m${normalizePath(file.replace(nsAbsolutePath, ""))}\x1b[0m ${action}, reloading page...`); + const timestamp = new Date().toLocaleTimeString(); + const filePath = nsRelativePath.replace(/^\.\/(?=.*)/, "") + normalizePath(file.replace(nsAbsolutePath, "")); + console.info(`${timestamp} \x1b[36m\x1b[1m[ns-plugin]\x1b[0m reloading page, \x1b[32m${filePath}\x1b[0m ${action}...`); namespaces = await getNameSpaces(nsEn); await server.moduleGraph.invalidateAll();