Depot API & v4 (#298)

* feat: nginx + torrential basics & services system

* fix: lint + i18n

* fix: update torrential to remove openssl

* feat: add torrential to Docker build

* feat: move to self hosted runner

* fix: move off self-hosted runner

* fix: update nginx.conf

* feat: torrential cache invalidation

* fix: update torrential for cache invalidation

* feat: integrity check task

* fix: lint

* feat: move to version ids

* fix: client fixes and client-side checks

* feat: new depot apis and version id fixes

* feat: update torrential

* feat: droplet bump and remove unsafe update functions

* fix: lint

* feat: v4 featureset: emulators, multi-launch commands

* fix: lint

* fix: mobile ui for game editor

* feat: launch options

* fix: lint

* fix: remove axios, use $fetch

* feat: metadata and task api improvements

* feat: task actions

* fix: slight styling issue

* feat: fix style and lints

* feat: totp backend routes

* feat: oidc groups

* fix: update drop-base

* feat: creation of passkeys & totp

* feat: totp signin

* feat: webauthn mfa/signin

* feat: launch selecting ui

* fix: manually running tasks

* feat: update add company game modal to use new SelectorGame

* feat: executor selector

* fix(docker): update rust to rust nightly for torrential build (#305)

* feat: new version ui

* feat: move package lookup to build time to allow for deno dev

* fix: lint

* feat: localisation cleanup

* feat: apply localisation cleanup

* feat: potential i18n refactor logic

* feat: remove args from commands

* fix: lint

* fix: lockfile

---------

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
This commit is contained in:
DecDuck
2026-01-13 15:32:39 +11:00
committed by GitHub
parent b6701f50e6
commit 038507fa74
190 changed files with 5848 additions and 2309 deletions
+32
View File
@@ -0,0 +1,32 @@
import type { Localisation } from "./utils";
import {
allLocalisableFiles,
flattenLocalisation,
keysFromContent,
stripEquivalence,
} from "./utils";
import fs from "node:fs";
const files = allLocalisableFiles();
const keySet = new Map<string, string>();
for (const file of files) {
const content = fs.readFileSync(file, "utf-8");
const keys = keysFromContent(content);
keys.forEach((key) => keySet.set(key, file));
}
const localeFile: Localisation = JSON.parse(
fs.readFileSync("./i18n/locales/en_us.json", "utf-8"),
);
const flattenedLocalisation = flattenLocalisation(localeFile);
for (const [key, file] of keySet.entries()) {
console.log(stripEquivalence(flattenedLocalisation.get(key)!));
if (!flattenedLocalisation.delete(key))
throw new Error(
`Found key "${key}" in file ${file} that doesn't exist in localisation`,
);
}
+54
View File
@@ -0,0 +1,54 @@
import fs from "node:fs";
import type { Localisation } from "./utils";
import {
allLocalisableFiles,
fetchLocalisation,
keysFromContent,
} from "./utils";
const files = allLocalisableFiles();
const localeFile: Localisation = JSON.parse(
fs.readFileSync("./i18n/locales/en_us.json", "utf-8"),
);
const keepPrefixes = ["error", "common", "chars"];
const keyMap: Map<string, string> = new Map();
for (const file of files) {
const content = fs.readFileSync(file, "utf-8");
const keys = keysFromContent(content);
const fileNoExtension = file.slice(0, file.lastIndexOf("."));
for (const key of keys) {
const _value = fetchLocalisation(localeFile, key);
const newKeySuffix = key.split(".").slice(-1); /*value
.replaceAll(/[^a-zA-Z\s]/g, "")
.toLowerCase()
.split(" ")
.slice(0, 3)
.map((v, i) =>
v
? i > 0
? v[0].toUpperCase() + v.slice(1)
: v
: key.split(".").slice(-1),
)
.join("");*/
const newKey = [
...fileNoExtension
.replaceAll(/[^a-zA-Z0-9/]/g, "")
.toLowerCase()
.split("/"),
newKeySuffix,
].join(".");
const finalKey = keepPrefixes.some((v) => key.startsWith(v)) ? key : newKey;
keyMap.set(key, finalKey);
}
}
console.log(keyMap);
+122
View File
@@ -0,0 +1,122 @@
import path from "node:path";
import fs from "node:fs";
import prettier from "prettier";
const prettierConfig = JSON.parse(
fs.readFileSync("./.prettierrc.json", "utf-8"),
);
const paths = ["./components", "./layouts", "./pages", "./server"];
const constPaths = ["error.vue", "app.vue"];
const extensions = [".vue", ".ts"];
function recursiveFindFiles(root: string): string[] {
const results = [];
const subpaths = fs.readdirSync(root);
for (const subpath of subpaths) {
const absPath = path.join(root, subpath);
if (extensions.some((v) => absPath.endsWith(v))) {
results.push(absPath);
continue;
}
const stat = fs.statSync(absPath);
if (stat.isDirectory()) {
results.push(...recursiveFindFiles(absPath));
continue;
}
}
return [...results, ...constPaths];
}
/**
* Fetches the paths of all files available to be localised
*/
export function allLocalisableFiles(): string[] {
const files = paths.map((k) => recursiveFindFiles(k)).flat();
return files;
}
const I18N_UTIL_REGEX = /(?<=[^a-zA-Z]t\(\s*?["']).*?(?=["'])/g;
const I18N_KEYPATH_REGEX = /(?<=keypath=["']).*?(?=["'])/g;
/**
* Uses regex to match all i18n keys in content
* @param content The file content to match against
*/
export function keysFromContent(content: string): string[] {
const matches = [
...content.matchAll(I18N_UTIL_REGEX),
...content.matchAll(I18N_KEYPATH_REGEX),
];
return matches.map((v) => v[0]);
}
export type Localisation = { [key: string]: Localisation | string };
export function flattenLocalisation(localisation: Localisation) {
const map = new Map<string, string>();
flattenLocalisationRecursive(map, [], localisation);
return map;
}
function flattenLocalisationRecursive(
map: Map<string, string>,
key: string[],
localisationBranch: Localisation | string,
) {
if (typeof localisationBranch === "string") {
map.set(key.join("."), localisationBranch);
return;
}
for (const [subKey, value] of Object.entries(localisationBranch)) {
const newKey = [...key, subKey];
flattenLocalisationRecursive(map, newKey, value);
}
}
export function deleteLocalisation(localisation: Localisation, key: string) {
const parts = key.split(".");
let current: Localisation | string = localisation;
for (const part of parts.slice(0, -1)) {
if (typeof current === "string")
throw new Error(`${key} not found in localisation`);
current = current[part];
}
if (typeof current === "string")
throw new Error(`${key} not found in localisation`);
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete current[parts.at(-1)!];
}
export function fetchLocalisation(
localisation: Localisation,
key: string,
): string {
const parts = key.split(".");
let current: Localisation | string = localisation;
for (const part of parts.slice(0, -1)) {
if (typeof current === "string")
throw new Error(`${key} not found in localisation`);
current = current[part];
}
if (typeof current === "string")
throw new Error(`${key} not found in localisation`);
return current[parts.at(-1)!] as string;
}
export async function writeJSON<T>(path: string, object: T) {
const flatStr = JSON.stringify(object);
const formatted = await prettier.format(flatStr, {
parser: "json",
...prettierConfig,
});
fs.writeFileSync(path, formatted);
}
/**
* Strips some sort of English language string down to something that can be compared to be basically equivalent
*/
export function stripEquivalence(value: string): string {
return value.replaceAll(/[.,\s]/g, "").toLowerCase();
}