Skip to content

Commit 4c4fffe

Browse files
committed
web/i81n: Clean up locale scripts.
1 parent 3296489 commit 4c4fffe

File tree

7 files changed

+190
-83
lines changed

7 files changed

+190
-83
lines changed

web/package.json

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
"private": true,
66
"scripts": {
77
"build": "wireit",
8-
"build-locales": "wireit",
9-
"build-locales:build": "wireit",
108
"build-proxy": "wireit",
9+
"build:locales": "wireit",
1110
"build:sfe": "npm run build -w @goauthentik/web-sfe",
12-
"esbuild:watch": "node scripts/build-web.mjs --watch",
13-
"extract-locales": "wireit",
11+
"bundler:watch": "node scripts/build-web.mjs --watch",
12+
"extract-locales": "lit-localize extract",
1413
"format": "wireit",
1514
"lint": "eslint --fix .",
1615
"lint-check": "eslint --max-warnings 0 .",
@@ -27,7 +26,7 @@
2726
"test": "vitest",
2827
"test:e2e": "playwright test",
2928
"tsc": "wireit",
30-
"watch": "run-s build-locales esbuild:watch"
29+
"watch": "run-s build:locales bundler:watch"
3130
},
3231
"type": "module",
3332
"exports": {
@@ -228,7 +227,7 @@
228227
"./dist/styles/**"
229228
],
230229
"dependencies": [
231-
"build-locales",
230+
"build:locales",
232231
"build:sfe"
233232
],
234233
"env": {
@@ -241,16 +240,13 @@
241240
"build-proxy": {
242241
"command": "node scripts/build-web.mjs --proxy",
243242
"dependencies": [
244-
"build-locales"
243+
"build:locales"
245244
]
246245
},
247-
"build-locales:build": {
248-
"command": "lit-localize build"
249-
},
250-
"build-locales:repair": {
246+
"locales:repair": {
251247
"command": "prettier --write ./src/locale-codes.ts"
252248
},
253-
"build-locales": {
249+
"build:locales": {
254250
"command": "node scripts/build-locales.mjs",
255251
"files": [
256252
"./xliff/*.xlf"
@@ -260,9 +256,6 @@
260256
"./src/locale-codes.ts"
261257
]
262258
},
263-
"extract-locales": {
264-
"command": "lit-localize extract"
265-
},
266259
"lint:components": {
267260
"command": "lit-analyzer src"
268261
},
@@ -272,7 +265,7 @@
272265
"NODE_OPTIONS": "--max_old_space_size=8192"
273266
},
274267
"dependencies": [
275-
"build-locales"
268+
"build:locales"
276269
]
277270
},
278271
"lint:lockfile": {
@@ -304,7 +297,7 @@
304297
"NODE_OPTIONS": "--max_old_space_size=8192"
305298
},
306299
"dependencies": [
307-
"build-locales"
300+
"build:locales"
308301
]
309302
}
310303
},

web/scripts/build-locales.mjs

Lines changed: 132 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,93 +11,176 @@
1111
* long spew of "this string is not translated" and replacing it with a
1212
* summary of how many strings are missing with respect to the source locale.
1313
*
14-
* @import { ConfigFile } from "@lit/localize-tools/lib/types/config.js"
1514
* @import { Stats } from "node:fs";
15+
* @import { RuntimeOutputConfig } from "@lit/localize-tools/lib/types/modes.js"
1616
*/
1717

18-
import { spawnSync } from "node:child_process";
19-
import { readFileSync, statSync } from "node:fs";
20-
import path from "node:path";
18+
import * as fs from "node:fs/promises";
19+
import path, { resolve } from "node:path";
2120

21+
import { generatePseudoLocaleModule } from "./pseudolocalize.mjs";
22+
23+
// import localizeRules from "../lit-localize.json" with { type: "json" };
24+
import { ConsoleLogger } from "#logger/node";
2225
import { PackageRoot } from "#paths/node";
2326

24-
/**
25-
* @type {ConfigFile}
26-
*/
27-
const localizeRules = JSON.parse(
28-
readFileSync(path.join(PackageRoot, "lit-localize.json"), "utf-8"),
27+
import { readConfigFileAndWriteSchema } from "@lit/localize-tools/lib/config.js";
28+
import { RuntimeLitLocalizer } from "@lit/localize-tools/lib/modes/runtime.js";
29+
30+
const logger = ConsoleLogger.child({ name: "Locales" });
31+
32+
const localizeRules = readConfigFileAndWriteSchema(path.join(PackageRoot, "lit-localize.json"));
33+
34+
if (localizeRules.interchange.format !== "xliff") {
35+
logger.error("Unsupported interchange type, expected 'xliff'");
36+
process.exit(1);
37+
}
38+
39+
const XLIFFPath = resolve(PackageRoot, localizeRules.interchange.xliffDir);
40+
41+
const EmittedLocalesDirectory = resolve(
42+
PackageRoot,
43+
/** @type {string} */ (localizeRules.output.outputDir),
2944
);
3045

46+
async function cleanEmittedLocales() {
47+
logger.info("♻️ Cleaning previously emitted locales...");
48+
logger.info(`♻️ ${EmittedLocalesDirectory}`);
49+
50+
await fs.rm(EmittedLocalesDirectory, {
51+
recursive: true,
52+
force: true,
53+
});
54+
55+
await fs.mkdir(EmittedLocalesDirectory, {
56+
recursive: true,
57+
});
58+
59+
logger.info(`♻️ Done!`);
60+
}
61+
3162
/**
63+
* Returns false if: the expected XLF file doesn't exist, The expected
64+
* generated file doesn't exist, or the XLF file is newer (has a higher date)
65+
* than the generated file. The missing XLF file is important enough it
66+
* generates a unique error message and halts the build.
3267
*
33-
* @param {string} loc
34-
* @returns {boolean}
68+
* @param {string} localeCode
69+
* @returns {Promise<boolean>}
3570
*/
36-
function generatedFileIsUpToDateWithXliffSource(loc) {
37-
const xliff = path.join("./xliff", `${loc}.xlf`);
38-
const gened = path.join("./src/locales", `${loc}.ts`);
39-
40-
// Returns false if: the expected XLF file doesn't exist, The expected
41-
// generated file doesn't exist, or the XLF file is newer (has a higher date)
42-
// than the generated file. The missing XLF file is important enough it
43-
// generates a unique error message and halts the build.
71+
async function checkIfEmittedFileCurrent(localeCode) {
72+
const xliffPath = path.join(XLIFFPath, `${localeCode}.xlf`);
73+
const emittedPath = path.join(EmittedLocalesDirectory, `${localeCode}.ts`);
4474

4575
/**
4676
* @type {Stats}
4777
*/
48-
let xlfStat;
78+
let xliffStat;
4979

5080
try {
51-
xlfStat = statSync(xliff);
81+
xliffStat = await fs.stat(xliffPath);
5282
} catch (_error) {
53-
console.error(`lit-localize expected '${loc}.xlf', but XLF file is not present`);
83+
logger.error(`XLIFF source file missing for locale '${localeCode}': ${xliffPath}`);
5484
process.exit(1);
5585
}
5686

5787
/**
5888
* @type {Stats}
5989
*/
60-
let genedStat;
90+
let emittedStat;
6191

6292
// If the generated file doesn't exist, of course it's not up to date.
6393
try {
64-
genedStat = statSync(gened);
94+
emittedStat = await fs.stat(emittedPath);
6595
} catch (_error) {
6696
return false;
6797
}
6898

69-
// if the generated file is the same age or newer (date is greater) than the xliff file, it's
99+
// Possible if the script was interrupted between clearing and generating.
100+
if (emittedStat.size === 0) {
101+
return false;
102+
}
103+
104+
// If the emitted file is the same age or newer (date is greater) than the xliff file, it's
70105
// presumed to have been generated by that file and is up-to-date.
71-
return genedStat.mtimeMs >= xlfStat.mtimeMs;
106+
return emittedStat.mtimeMs >= xliffStat.mtimeMs;
72107
}
73108

74-
// For all the expected files, find out if any aren't up-to-date.
75-
const upToDate = localizeRules.targetLocales.reduce(
76-
(acc, loc) => acc && generatedFileIsUpToDateWithXliffSource(loc),
77-
true,
78-
);
109+
/**
110+
* Checks if all the locale source files are up-to-date with their XLIFF sources.
111+
* @returns {Promise<boolean>}
112+
*/
113+
async function checkIfLocalesAreCurrent() {
114+
logger.info("Reading locale configuration...");
115+
116+
const targetLocales = localizeRules.targetLocales.filter((localeCode) => {
117+
return localeCode !== "pseudo-LOCALE";
118+
});
119+
120+
logger.info(`Checking ${targetLocales.length} source files...`);
121+
122+
let outOfDateCount = 0;
123+
124+
await Promise.all(
125+
targetLocales.map(async (localeCode) => {
126+
const current = await checkIfEmittedFileCurrent(localeCode);
79127

80-
if (!upToDate) {
81-
const status = spawnSync("npm", ["run", "build-locales:build"], { encoding: "utf8" });
128+
if (!current) {
129+
logger.info(`Locale '${localeCode}' is out-of-date.`);
130+
outOfDateCount++;
131+
}
132+
}),
133+
);
82134

83-
// Count all the missing message warnings
84-
const counts = status.stderr.split("\n").reduce((acc, line) => {
85-
const match = /^([\w-]+) message/.exec(line);
86-
if (!match) {
87-
return acc;
88-
}
89-
acc.set(match[1], (acc.get(match[1]) || 0) + 1);
90-
return acc;
91-
}, new Map());
135+
return outOfDateCount === 0;
136+
}
137+
138+
export async function generateLocaleModules() {
139+
logger.info("Updating pseudo-locale...");
140+
await generatePseudoLocaleModule();
141+
142+
logger.info("Generating locale modules...");
92143

93-
const locales = Array.from(counts.keys());
94-
locales.sort();
144+
const localizer = new RuntimeLitLocalizer({
145+
...localizeRules,
146+
output: /** @type {RuntimeOutputConfig} */ (localizeRules.output),
147+
});
95148

96-
const report = locales
97-
.map((locale) => `Locale '${locale}' has ${counts.get(locale)} missing translations`)
98-
.join("\n");
149+
await localizer.build();
99150

100-
console.log(`Translation tables rebuilt.\n${report}\n`);
151+
logger.info("Complete.");
101152
}
102153

103-
console.log("Locale ./src is up-to-date");
154+
async function delegateCommand() {
155+
const command = process.argv[2];
156+
157+
switch (command) {
158+
case "--clean":
159+
return cleanEmittedLocales();
160+
case "--check":
161+
return checkIfLocalesAreCurrent();
162+
case "--force":
163+
return generateLocaleModules();
164+
}
165+
166+
const upToDate = await checkIfLocalesAreCurrent();
167+
168+
if (upToDate) {
169+
logger.info("Locale is up-to-date!");
170+
171+
return;
172+
}
173+
174+
logger.info("Locale ./src is out-of-date, rebuilding...");
175+
176+
return generateLocaleModules();
177+
}
178+
179+
await delegateCommand()
180+
.then(() => {
181+
process.exit(0);
182+
})
183+
.catch((error) => {
184+
logger.error(`Error during locale build: ${error}`);
185+
process.exit(1);
186+
});

web/scripts/pseudolocalize.mjs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111

1212
import { readFileSync } from "node:fs";
1313
import path from "node:path";
14+
import { fileURLToPath } from "node:url";
1415

1516
import { PackageRoot } from "#paths/node";
1617

18+
import { isMain } from "@goauthentik/core/scripting/node";
19+
1720
import pseudolocale from "pseudolocale";
1821

1922
import { makeFormatter } from "@lit/localize-tools/lib/formatters/index.js";
@@ -22,6 +25,7 @@ import { TransformLitLocalizer } from "@lit/localize-tools/lib/modes/transform.j
2225

2326
const pseudoLocale = /** @type {Locale} */ ("pseudo-LOCALE");
2427
const targetLocales = [pseudoLocale];
28+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
2529

2630
/**
2731
* @type {ConfigFile}
@@ -59,10 +63,16 @@ const pseudoMessagify = (message) => ({
5963
),
6064
});
6165

62-
const localizer = new TransformLitLocalizer(config);
63-
const { messages } = localizer.extractSourceMessages();
64-
const translations = messages.map(pseudoMessagify);
65-
const sorted = sortProgramMessages([...messages]);
66-
const formatter = makeFormatter(config);
66+
export async function generatePseudoLocaleModule() {
67+
const localizer = new TransformLitLocalizer(config);
68+
const { messages } = localizer.extractSourceMessages();
69+
const translations = messages.map(pseudoMessagify);
70+
const sorted = sortProgramMessages([...messages]);
71+
const formatter = makeFormatter(config);
72+
73+
await formatter.writeOutput(sorted, new Map([[pseudoLocale, translations]]));
74+
}
6775

68-
formatter.writeOutput(sorted, new Map([[pseudoLocale, translations]]));
76+
if (isMain(import.meta)) {
77+
generatePseudoLocaleModule();
78+
}

web/src/elements/ak-locale-context/definitions.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { AkLocale, LocaleRow } from "./types.js";
22

3-
import * as _enLocale from "#locales/en";
4-
53
import type { LocaleModule } from "@lit/localize";
64
import { msg } from "@lit/localize";
75

86
export const DEFAULT_FALLBACK = "en";
97

10-
const enLocale: LocaleModule = _enLocale;
11-
12-
export { enLocale };
8+
export const enLocale: LocaleModule = {
9+
templates: {},
10+
};
1311

1412
// NOTE: This table cannot be made any shorter, despite all the repetition of syntax. Bundlers look
1513
// for the `import` #a *string target* for doing alias substitution, so putting
@@ -43,7 +41,7 @@ const debug: LocaleRow = [
4341
// prettier-ignore
4442
const LOCALE_TABLE: LocaleRow[] = [
4543
["de", /^de([_-]|$)/i, () => msg("German"), () => import("#locales/de")],
46-
["en", /^en([_-]|$)/i, () => msg("English"), () => import("#locales/en")],
44+
["en", /^en([_-]|$)/i, () => msg("English"), () => Promise.resolve(enLocale)],
4745
["es", /^es([_-]|$)/i, () => msg("Spanish"), () => import("#locales/es")],
4846
["fr", /^fr([_-]|$)/i, () => msg("French"), () => import("#locales/fr")],
4947
["it", /^it([_-]|$)/i, () => msg("Italian"), () => import("#locales/it")],

0 commit comments

Comments
 (0)