Merge 27318bac69
into 110fd2f0a1
This commit is contained in:
commit
81dd9cc033
|
@ -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 "./utils-plugins";
|
||||
|
||||
//#region Interfaces/Types
|
||||
|
||||
|
@ -90,18 +91,6 @@ const fonts: Array<LoadingFontFaceProperty> = [
|
|||
},
|
||||
];
|
||||
|
||||
/** 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
|
||||
|
||||
|
@ -136,6 +125,8 @@ function i18nMoneyFormatter(amount: any): string {
|
|||
return `@[MONEY]{${i18next.t("common:money", { amount })}}`;
|
||||
}
|
||||
|
||||
const nsEn = [];
|
||||
|
||||
//#region Exports
|
||||
|
||||
/**
|
||||
|
@ -157,7 +148,9 @@ export async function initI18n(): Promise<void> {
|
|||
* 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 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.
|
||||
*
|
||||
|
@ -189,99 +182,7 @@ export async function initI18n(): Promise<void> {
|
|||
},
|
||||
},
|
||||
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",
|
||||
"filterText",
|
||||
"gameMode",
|
||||
"gameStatsUiHandler",
|
||||
"growth",
|
||||
"menu",
|
||||
"menuUiHandler",
|
||||
"modifier",
|
||||
"modifierType",
|
||||
"move",
|
||||
"nature",
|
||||
"pokeball",
|
||||
"pokedexUiHandler",
|
||||
"pokemon",
|
||||
"pokemonEvolutions",
|
||||
"pokemonForm",
|
||||
"pokemonInfo",
|
||||
"pokemonInfoContainer",
|
||||
"pokemonSummary",
|
||||
"saveSlotSelectUiHandler",
|
||||
"settings",
|
||||
"splashMessages",
|
||||
"starterSelectUiHandler",
|
||||
"statusEffect",
|
||||
"terrain",
|
||||
"titles",
|
||||
"trainerClasses",
|
||||
"trainersCommon",
|
||||
"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, // assigned with #app/plugins/vite/namespaces-i18n-plugin.ts
|
||||
detection: {
|
||||
lookupLocalStorage: "prLang",
|
||||
},
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
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
|
||||
*
|
||||
* @remarks 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",
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform 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: string, dir: string): boolean {
|
||||
const filePath = path.normalize(file);
|
||||
const dirPath = path.normalize(dir);
|
||||
return filePath.startsWith(dirPath);
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import { normalizePath, type Plugin as VitePlugin } from "vite";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import "#app/plugins/utils-plugins";
|
||||
import { objectSwap, namespaceMap, kebabCaseToCamelCase, isFileInsideDir } from "#app/plugins/utils-plugins";
|
||||
|
||||
const namespaceMapSwap = objectSwap(namespaceMap);
|
||||
|
||||
/**
|
||||
* Crawl a directory recursively for json files to return their name with camelCase format.
|
||||
* Also if file is in directory returns format "dir/fileName" format
|
||||
* @param dir - The directory to crawl
|
||||
*/
|
||||
function getNameSpaces(dir: string): 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()) {
|
||||
processDirectory(file, filePath, namespace);
|
||||
} else if (path.extname(file) === ".json") {
|
||||
processJsonFile(file, namespace);
|
||||
}
|
||||
}
|
||||
|
||||
return namespace;
|
||||
}
|
||||
|
||||
function processDirectory(file: string, filePath: string, namespace: string[]) {
|
||||
const subnamespace = getNameSpaces(filePath);
|
||||
for (let i = 0; i < subnamespace.length; i++) {
|
||||
let ns = subnamespace[i];
|
||||
if (namespaceMapSwap[file.replace(".json", "")]) {
|
||||
ns = namespaceMapSwap[file.replace(".json", "")];
|
||||
} else if (kebabCaseToCamelCase(file).replace(".json", "").startsWith("mysteryEncounters")) {
|
||||
ns = subnamespace[i].replace(/Dialogue$/, "");
|
||||
}
|
||||
// format "directory/namespace" for namespace in folder
|
||||
namespace.push(`${kebabCaseToCamelCase(file).replace(".json", "")}/${ns}`);
|
||||
}
|
||||
}
|
||||
|
||||
function processJsonFile(file: string, namespace: string[]) {
|
||||
let ns = kebabCaseToCamelCase(file).replace(".json", "");
|
||||
if (namespaceMapSwap[file.replace(".json", "")]) {
|
||||
ns = namespaceMapSwap[file.replace(".json", "")];
|
||||
}
|
||||
namespace.push(ns);
|
||||
}
|
||||
|
||||
export function LocaleNamespace(): VitePlugin {
|
||||
const nsRelativePath = "./public/locales";
|
||||
const nsEn = nsRelativePath + "/en"; // Default namespace
|
||||
let namespaces = getNameSpaces(nsEn);
|
||||
const nsAbsolutePath = path.resolve(process.cwd(), nsRelativePath);
|
||||
|
||||
return {
|
||||
name: "namespaces-i18next",
|
||||
buildStart() {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
console.log("Collect namespaces");
|
||||
}
|
||||
},
|
||||
configureServer(server) {
|
||||
const restartHandler = async (file: string, action: string) => {
|
||||
/*
|
||||
* If any JSON file in nsLocation is created/modified..
|
||||
* refresh the page to update the namespaces of i18next
|
||||
*/
|
||||
if (isFileInsideDir(file, nsAbsolutePath) && file.endsWith(".json")) {
|
||||
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 = getNameSpaces(nsEn);
|
||||
server.moduleGraph.invalidateAll();
|
||||
server.ws.send({
|
||||
type: "full-reload",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
server.watcher
|
||||
.on("change", file => restartHandler(file, "updated"))
|
||||
.on("add", file => restartHandler(file, "added"))
|
||||
.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;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import { defineConfig, loadEnv, type Rollup, type 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)],
|
||||
plugins: [tsconfigPaths(), minifyJsonPlugin(["images", "battle-anims"], true), LocaleNamespace()],
|
||||
clearScreen: false,
|
||||
appType: "mpa",
|
||||
build: {
|
||||
|
|
Loading…
Reference in New Issue