mirror of
https://github.com/goauthentik/authentik
synced 2026-04-25 17:15:26 +02:00
* Fix alignment, focus. * Clean up. * Tidy click area. * Fix compatibility mode. * Fix alignment. * Fix issues surrounding labels, alignment, consistency. * Update web/src/common/ui/locale/format.ts Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> * Tidy hover states. * Tidy. * Clean up parsing. * Tidy comments, usage. * Always use script naming over region. * Remove unused. * Spacing. --------- Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
273 lines
7.4 KiB
JavaScript
273 lines
7.4 KiB
JavaScript
/// <reference types="node" />
|
|
|
|
/**
|
|
* @file Lit Localize build script.
|
|
*
|
|
* @remarks
|
|
* Determines if all the Xliff translation source files are present and
|
|
* if the Typescript source files generated from those sources are up-to-date.
|
|
*
|
|
* If they are not, it runs the locale building script, intercepting the
|
|
* long spew of "this string is not translated" and replacing it with a
|
|
* summary of how many strings are missing with respect to the source locale.
|
|
*
|
|
* @import { Stats } from "node:fs";
|
|
*/
|
|
|
|
import * as fs from "node:fs/promises";
|
|
import path, { resolve } from "node:path";
|
|
|
|
import { generatePseudoLocaleModule } from "./pseudolocalize.mjs";
|
|
|
|
import { ConsoleLogger } from "#logger/node";
|
|
import { PackageRoot } from "#paths/node";
|
|
|
|
import { readConfigFileAndWriteSchema } from "@lit/localize-tools/lib/config.js";
|
|
import { RuntimeLitLocalizer } from "@lit/localize-tools/lib/modes/runtime.js";
|
|
|
|
//#region Setup
|
|
|
|
const missingMessagePattern = /([\w_-]+)\smessage\s(?:[\w_.-]+)\sis\smissing/;
|
|
const outdatedMessagePattern = /([\w_-]+)\smessage\s(?:[\w_.-]+)\sdoes\snot\sexist/;
|
|
const logger = ConsoleLogger.child({ name: "Locales" });
|
|
|
|
const localizeRules = readConfigFileAndWriteSchema(path.join(PackageRoot, "lit-localize.json"));
|
|
|
|
if (localizeRules.interchange.format !== "xliff") {
|
|
logger.error("Unsupported interchange type, expected 'xliff'");
|
|
process.exit(1);
|
|
}
|
|
|
|
const { sourceLocale } = localizeRules;
|
|
|
|
localizeRules.targetLocales = localizeRules.targetLocales.filter((locale) => {
|
|
return locale !== sourceLocale;
|
|
});
|
|
|
|
const XLIFFPath = resolve(PackageRoot, localizeRules.interchange.xliffDir);
|
|
|
|
const EmittedLocalesDirectory = resolve(
|
|
PackageRoot,
|
|
/** @type {string} */ (localizeRules.output.outputDir),
|
|
);
|
|
|
|
const targetLocales = localizeRules.targetLocales.filter((localeCode) => {
|
|
return localeCode !== "en-XA";
|
|
});
|
|
|
|
//#endregion
|
|
|
|
//#region Utilities
|
|
|
|
/**
|
|
* Cleans the emitted locales directory.
|
|
*/
|
|
async function cleanEmittedLocales() {
|
|
logger.info("♻️ Cleaning previously emitted locales...");
|
|
logger.info(`♻️ ${EmittedLocalesDirectory}`);
|
|
|
|
await fs.rm(EmittedLocalesDirectory, {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
|
|
await fs.mkdir(EmittedLocalesDirectory, {
|
|
recursive: true,
|
|
});
|
|
|
|
logger.info(`♻️ Done!`);
|
|
}
|
|
|
|
/**
|
|
* Returns false if: the expected XLF file doesn't exist, The expected
|
|
* generated file doesn't exist, or the XLF file is newer (has a higher date)
|
|
* than the generated file. The missing XLF file is important enough it
|
|
* generates a unique error message and halts the build.
|
|
*
|
|
* @param {string} localeCode
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async function checkIfEmittedFileCurrent(localeCode) {
|
|
const xliffPath = path.join(XLIFFPath, `${localeCode}.xlf`);
|
|
const emittedPath = path.join(EmittedLocalesDirectory, `${localeCode}.ts`);
|
|
|
|
/**
|
|
* @type {Stats}
|
|
*/
|
|
let xliffStat;
|
|
|
|
try {
|
|
xliffStat = await fs.stat(xliffPath);
|
|
} catch (_error) {
|
|
logger.error(`XLIFF source file missing for locale '${localeCode}': ${xliffPath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
/**
|
|
* @type {Stats}
|
|
*/
|
|
let emittedStat;
|
|
|
|
// If the generated file doesn't exist, of course it's not up to date.
|
|
try {
|
|
emittedStat = await fs.stat(emittedPath);
|
|
} catch (_error) {
|
|
return false;
|
|
}
|
|
|
|
// Possible if the script was interrupted between clearing and generating.
|
|
if (emittedStat.size === 0) {
|
|
return false;
|
|
}
|
|
|
|
// If the emitted file is the same age or newer (date is greater) than the xliff file, it's
|
|
// presumed to have been generated by that file and is up-to-date.
|
|
return emittedStat.mtimeMs >= xliffStat.mtimeMs;
|
|
}
|
|
|
|
/**
|
|
* Checks if all the locale source files are up-to-date with their XLIFF sources.
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async function checkIfLocalesAreCurrent() {
|
|
logger.info("Reading locale configuration...");
|
|
|
|
logger.info(`Checking ${targetLocales.length} source files...`);
|
|
|
|
let outOfDateCount = 0;
|
|
|
|
await Promise.all(
|
|
targetLocales.map(async (localeCode) => {
|
|
const current = await checkIfEmittedFileCurrent(localeCode);
|
|
|
|
if (!current) {
|
|
logger.info(`Locale '${localeCode}' is out-of-date.`);
|
|
outOfDateCount++;
|
|
}
|
|
}),
|
|
);
|
|
|
|
return outOfDateCount === 0;
|
|
}
|
|
|
|
export async function generateLocaleModules() {
|
|
logger.info("Updating pseudo-locale...");
|
|
await generatePseudoLocaleModule();
|
|
|
|
logger.info("Generating locale modules...");
|
|
|
|
/**
|
|
* @type {Map<string, number>}
|
|
*/
|
|
const missingTranslationWarnings = new Map();
|
|
|
|
/**
|
|
* @type {Map<string, number>}
|
|
*/
|
|
const outdatedTranslationWarnings = new Map();
|
|
|
|
const initialConsoleWarn = console.warn;
|
|
|
|
console.warn = (arg0, ...args) => {
|
|
if (typeof arg0 !== "string") {
|
|
initialConsoleWarn(arg0, ...args);
|
|
return;
|
|
}
|
|
|
|
const [, matchedMissingTranslation] = arg0.match(missingMessagePattern) || [];
|
|
|
|
if (matchedMissingTranslation) {
|
|
const count = missingTranslationWarnings.get(matchedMissingTranslation) || 0;
|
|
|
|
missingTranslationWarnings.set(matchedMissingTranslation, count + 1);
|
|
|
|
logger.debug(arg0);
|
|
|
|
return;
|
|
}
|
|
|
|
const [, matchedOutdatedTranslation] = arg0.match(outdatedMessagePattern) || [];
|
|
|
|
if (matchedOutdatedTranslation) {
|
|
const count = outdatedTranslationWarnings.get(matchedOutdatedTranslation) || 0;
|
|
|
|
outdatedTranslationWarnings.set(matchedOutdatedTranslation, count + 1);
|
|
|
|
logger.debug(arg0);
|
|
|
|
return;
|
|
}
|
|
|
|
initialConsoleWarn(arg0, ...args);
|
|
};
|
|
|
|
// @ts-expect-error: Type is too broad.
|
|
const localizer = new RuntimeLitLocalizer(localizeRules);
|
|
|
|
await localizer.build();
|
|
|
|
const missingTranslationsReport = Array.from(missingTranslationWarnings)
|
|
.filter(([, count]) => count)
|
|
.sort(([, totalsA], [, totalsB]) => {
|
|
return totalsB - totalsA;
|
|
})
|
|
.map(([locale, count]) => `${locale}: ${count.toLocaleString()}`)
|
|
.join("\n");
|
|
|
|
logger.info(`Missing translations:\n${missingTranslationsReport || "None"}`);
|
|
|
|
const outdatedTranslationsReport = Array.from(outdatedTranslationWarnings)
|
|
.filter(([, count]) => count)
|
|
.sort(([, totalsA], [, totalsB]) => {
|
|
return totalsB - totalsA;
|
|
})
|
|
.map(([locale, count]) => `${locale}: ${count.toLocaleString()}`)
|
|
.join("\n");
|
|
|
|
logger.info(`Outdated translations:\n${outdatedTranslationsReport || "None"}`);
|
|
|
|
localizer.assertTranslationsAreValid();
|
|
|
|
logger.info("Complete.");
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Commands
|
|
|
|
async function delegateCommand() {
|
|
const command = process.argv[2];
|
|
|
|
switch (command) {
|
|
case "--clean":
|
|
return cleanEmittedLocales();
|
|
case "--check":
|
|
return checkIfLocalesAreCurrent();
|
|
case "--force":
|
|
return cleanEmittedLocales().then(generateLocaleModules);
|
|
}
|
|
|
|
const upToDate = await checkIfLocalesAreCurrent();
|
|
|
|
if (upToDate) {
|
|
logger.info("Locale is up-to-date!");
|
|
|
|
return;
|
|
}
|
|
|
|
logger.info("Locale ./src is out-of-date, rebuilding...");
|
|
|
|
return generateLocaleModules();
|
|
}
|
|
|
|
await delegateCommand()
|
|
.then(() => {
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
logger.error(`Error during locale build: ${error}`);
|
|
process.exit(1);
|
|
});
|
|
|
|
//#endregion
|