drop/server/i18n/scripts/utils.ts

123 lines
3.5 KiB
TypeScript
Raw Permalink Normal View History

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>
2026-01-13 15:32:39 +11:00
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();
}