zotero-plugin-scaffold 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { l as logger, E as ExitSignals, C as Config, B as Build, S as Serve, T as Test, R as Release } from './shared/zotero-plugin-scaffold.
|
|
3
|
+
import { l as logger, E as ExitSignals, C as Config, B as Build, S as Serve, T as Test, R as Release } from './shared/zotero-plugin-scaffold.C-SXQX4J.mjs';
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { pathExists } from 'fs-extra';
|
|
6
6
|
import tinyUpdateNotifier from 'tiny-update-notifier';
|
|
@@ -13,11 +13,12 @@ import 'node:util';
|
|
|
13
13
|
import 'std-env';
|
|
14
14
|
import './shared/zotero-plugin-scaffold.v8dJZZ-v.mjs';
|
|
15
15
|
import 'tinyglobby';
|
|
16
|
-
import 'node:fs';
|
|
17
16
|
import 'node:path';
|
|
18
17
|
import 'adm-zip';
|
|
19
18
|
import 'esbuild';
|
|
20
19
|
import 'node:crypto';
|
|
20
|
+
import 'node:fs';
|
|
21
|
+
import '@fluent/syntax';
|
|
21
22
|
import '@swc/core';
|
|
22
23
|
import 'node:child_process';
|
|
23
24
|
import 'bumpp';
|
|
@@ -31,7 +32,7 @@ import 'xvfb-ts';
|
|
|
31
32
|
import 'node:http';
|
|
32
33
|
|
|
33
34
|
const name = "zotero-plugin-scaffold";
|
|
34
|
-
const version = "0.
|
|
35
|
+
const version = "0.6.0";
|
|
35
36
|
const pkg = {
|
|
36
37
|
name: name,
|
|
37
38
|
version: version};
|
package/dist/index.d.mts
CHANGED
|
@@ -315,6 +315,20 @@ interface BuildConfig {
|
|
|
315
315
|
* @default []
|
|
316
316
|
*/
|
|
317
317
|
ignore: string[];
|
|
318
|
+
/**
|
|
319
|
+
* Generate i10n.d.ts form fluent messages.
|
|
320
|
+
*
|
|
321
|
+
* - false: disable
|
|
322
|
+
* - string: path of dts file
|
|
323
|
+
*
|
|
324
|
+
* 为 Fluent messages 生成类型声明文件。
|
|
325
|
+
*
|
|
326
|
+
* - false: 禁用
|
|
327
|
+
* - string:dts 文件路径
|
|
328
|
+
*
|
|
329
|
+
* @default 'typings/i10n.d.ts'
|
|
330
|
+
*/
|
|
331
|
+
dts: string | false;
|
|
318
332
|
};
|
|
319
333
|
prefs: {
|
|
320
334
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as Build, C as Config, R as Release, S as Serve, T as Test, d as defineConfig } from './shared/zotero-plugin-scaffold.
|
|
1
|
+
export { B as Build, C as Config, R as Release, S as Serve, T as Test, d as defineConfig } from './shared/zotero-plugin-scaffold.C-SXQX4J.mjs';
|
|
2
2
|
import 'c12';
|
|
3
3
|
import 'es-toolkit';
|
|
4
4
|
import 'fs-extra/esm';
|
|
@@ -10,11 +10,12 @@ import 'std-env';
|
|
|
10
10
|
import './shared/zotero-plugin-scaffold.v8dJZZ-v.mjs';
|
|
11
11
|
import 'node:fs/promises';
|
|
12
12
|
import 'tinyglobby';
|
|
13
|
-
import 'node:fs';
|
|
14
13
|
import 'node:path';
|
|
15
14
|
import 'adm-zip';
|
|
16
15
|
import 'esbuild';
|
|
17
16
|
import 'node:crypto';
|
|
17
|
+
import 'node:fs';
|
|
18
|
+
import '@fluent/syntax';
|
|
18
19
|
import '@swc/core';
|
|
19
20
|
import 'node:child_process';
|
|
20
21
|
import 'bumpp';
|
package/dist/shared/{zotero-plugin-scaffold.DGHktPRh.mjs → zotero-plugin-scaffold.C-SXQX4J.mjs}
RENAMED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { loadConfig as loadConfig$1 } from 'c12';
|
|
2
2
|
import { isPlainObject, kebabCase, mapValues, toMerged, escapeRegExp, debounce, delay } from 'es-toolkit';
|
|
3
|
-
import { readJsonSync, outputFile, emptyDir, copy, readJSON, outputJSON,
|
|
3
|
+
import { readJsonSync, move, outputFile, emptyDir, copy, readJSON, outputJSON, writeJson, pathExists, ensureDir, remove } from 'fs-extra/esm';
|
|
4
4
|
import { createHooks } from 'hookable';
|
|
5
5
|
import process from 'node:process';
|
|
6
6
|
import readline from 'node:readline';
|
|
7
7
|
import util from 'node:util';
|
|
8
8
|
import { isDebug, isCI, isWindows, isMacOS, isLinux } from 'std-env';
|
|
9
9
|
import { p as parseRepoUrl, d as dateFormat, t as template, b as toArray, a as replaceInFile } from './zotero-plugin-scaffold.v8dJZZ-v.mjs';
|
|
10
|
-
import {
|
|
11
|
-
import { readFile, writeFile, stat } from 'node:fs/promises';
|
|
12
|
-
import { basename, dirname, join, resolve, extname } from 'node:path';
|
|
10
|
+
import { dirname, basename, join, resolve, extname } from 'node:path';
|
|
13
11
|
import AdmZip from 'adm-zip';
|
|
14
12
|
import { build, context } from 'esbuild';
|
|
15
13
|
import { glob, globSync } from 'tinyglobby';
|
|
16
14
|
import { createHash } from 'node:crypto';
|
|
15
|
+
import { createReadStream, existsSync } from 'node:fs';
|
|
16
|
+
import { readFile, writeFile, stat } from 'node:fs/promises';
|
|
17
|
+
import { parse, serialize, Transformer } from '@fluent/syntax';
|
|
17
18
|
import { parseSync, printSync } from '@swc/core';
|
|
18
19
|
import { execSync, spawn } from 'node:child_process';
|
|
19
20
|
import { versionBump, ProgressEvent } from 'bumpp';
|
|
@@ -264,7 +265,8 @@ const defaultConfig = {
|
|
|
264
265
|
fluent: {
|
|
265
266
|
prefixFluentMessages: true,
|
|
266
267
|
prefixLocaleFiles: true,
|
|
267
|
-
ignore: []
|
|
268
|
+
ignore: [],
|
|
269
|
+
dts: "typings/i10n.d.ts"
|
|
268
270
|
},
|
|
269
271
|
prefs: {
|
|
270
272
|
prefix: "",
|
|
@@ -359,6 +361,205 @@ function generateHash(filePath, algorithm) {
|
|
|
359
361
|
});
|
|
360
362
|
}
|
|
361
363
|
|
|
364
|
+
class Base {
|
|
365
|
+
ctx;
|
|
366
|
+
constructor(ctx) {
|
|
367
|
+
this.ctx = ctx;
|
|
368
|
+
}
|
|
369
|
+
get logger() {
|
|
370
|
+
return this.ctx.logger;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function buildLocale(dist, namespace, options) {
|
|
375
|
+
const ignores = toArray(options.ignore);
|
|
376
|
+
const localeNames = await getLocales(dist);
|
|
377
|
+
const messageManager = new MessageManager(ignores);
|
|
378
|
+
await Promise.all(localeNames.map(async (locale) => {
|
|
379
|
+
const paths = await glob(`${dist}/addon/locale/${locale}/**/*.ftl`);
|
|
380
|
+
await Promise.all(paths.map(async (path) => {
|
|
381
|
+
const fm = new FluentManager();
|
|
382
|
+
await fm.read(path);
|
|
383
|
+
messageManager.addMessages(locale, fm.getMessages());
|
|
384
|
+
if (options.prefixFluentMessages) {
|
|
385
|
+
fm.prefixMessages(namespace);
|
|
386
|
+
await fm.write(path);
|
|
387
|
+
}
|
|
388
|
+
if (options.prefixLocaleFiles) {
|
|
389
|
+
const newPath = `${dirname(path)}/${namespace}-${basename(path)}`;
|
|
390
|
+
await move(path, newPath);
|
|
391
|
+
logger.debug(`Renamed FTL: ${path} \u2192 ${newPath}`);
|
|
392
|
+
}
|
|
393
|
+
}));
|
|
394
|
+
}));
|
|
395
|
+
const htmlPaths = await glob([`${dist}/addon/**/*.xhtml`, `${dist}/addon/**/*.html`]);
|
|
396
|
+
await Promise.all(htmlPaths.map(async (htmlPath) => {
|
|
397
|
+
const content = await readFile(htmlPath, "utf-8");
|
|
398
|
+
const { processedContent, foundMessages } = processHTMLFile(
|
|
399
|
+
content,
|
|
400
|
+
namespace,
|
|
401
|
+
messageManager.getFTLMessages(),
|
|
402
|
+
ignores,
|
|
403
|
+
htmlPath
|
|
404
|
+
);
|
|
405
|
+
messageManager.addMessages("html", foundMessages);
|
|
406
|
+
if (options.prefixFluentMessages) {
|
|
407
|
+
await writeFile(htmlPath, processedContent);
|
|
408
|
+
}
|
|
409
|
+
}));
|
|
410
|
+
messageManager.validateMessages();
|
|
411
|
+
if (options.dts) {
|
|
412
|
+
const dtsContent = generateFluentDts([...messageManager.getFTLMessages()]);
|
|
413
|
+
await outputFile(options.dts, dtsContent, "utf-8");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async function getLocales(dist) {
|
|
417
|
+
const localePaths = await glob(`${dist}/addon/locale/*`, { onlyDirectories: true });
|
|
418
|
+
return localePaths.map((p) => basename(p));
|
|
419
|
+
}
|
|
420
|
+
class FluentManager {
|
|
421
|
+
source;
|
|
422
|
+
resource;
|
|
423
|
+
messages = [];
|
|
424
|
+
constructor() {
|
|
425
|
+
}
|
|
426
|
+
// Parse Fluent source into an AST and extract messages
|
|
427
|
+
parse(source) {
|
|
428
|
+
this.source = source;
|
|
429
|
+
this.resource = parse(source, {});
|
|
430
|
+
}
|
|
431
|
+
// Read a file, parse its content, and extract messages
|
|
432
|
+
async read(path) {
|
|
433
|
+
const content = await readFile(path, "utf-8");
|
|
434
|
+
this.parse(content);
|
|
435
|
+
}
|
|
436
|
+
// Extract message IDs from the parsed resource
|
|
437
|
+
getMessages() {
|
|
438
|
+
if (!this.resource) {
|
|
439
|
+
throw new Error("Resource must be parsed first.");
|
|
440
|
+
}
|
|
441
|
+
this.messages.length = 0;
|
|
442
|
+
this.messages.push(
|
|
443
|
+
...this.resource.body.filter((entry) => entry.type === "Message").map((message) => message.id.name)
|
|
444
|
+
);
|
|
445
|
+
return this.messages;
|
|
446
|
+
}
|
|
447
|
+
// Apply namespace prefix to message IDs in the resource
|
|
448
|
+
prefixMessages(namespace) {
|
|
449
|
+
if (!this.resource) {
|
|
450
|
+
throw new Error("Resource must be parsed before applying prefix.");
|
|
451
|
+
}
|
|
452
|
+
new FluentTransformer(namespace).genericVisit(this.resource);
|
|
453
|
+
}
|
|
454
|
+
// Serialize the resource back into a string
|
|
455
|
+
serialize() {
|
|
456
|
+
if (!this.resource) {
|
|
457
|
+
throw new Error("Resource not parsed. Cannot serialize.");
|
|
458
|
+
}
|
|
459
|
+
return serialize(this.resource, {});
|
|
460
|
+
}
|
|
461
|
+
// Write the serialized resource to a file
|
|
462
|
+
async write(path) {
|
|
463
|
+
const result = this.serialize();
|
|
464
|
+
if (result !== this.source)
|
|
465
|
+
await writeFile(path, this.serialize());
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
class FluentTransformer extends Transformer {
|
|
469
|
+
constructor(prefix) {
|
|
470
|
+
super();
|
|
471
|
+
this.prefix = prefix;
|
|
472
|
+
}
|
|
473
|
+
needsPrefix(name) {
|
|
474
|
+
return !!this.prefix && !name.startsWith(this.prefix);
|
|
475
|
+
}
|
|
476
|
+
visitMessage(node) {
|
|
477
|
+
if (this.needsPrefix(node.id.name)) {
|
|
478
|
+
node.id.name = `${this.prefix}-${node.id.name}`;
|
|
479
|
+
}
|
|
480
|
+
return this.genericVisit(node);
|
|
481
|
+
}
|
|
482
|
+
visitMessageReference(node) {
|
|
483
|
+
if (this.needsPrefix(node.id.name)) {
|
|
484
|
+
node.id.name = `${this.prefix}-${node.id.name}`;
|
|
485
|
+
}
|
|
486
|
+
return this.genericVisit(node);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
class MessageManager {
|
|
490
|
+
ftlMessages = /* @__PURE__ */ new Map();
|
|
491
|
+
htmlMessages = /* @__PURE__ */ new Set();
|
|
492
|
+
ignores;
|
|
493
|
+
constructor(ignores) {
|
|
494
|
+
this.ignores = ignores;
|
|
495
|
+
}
|
|
496
|
+
// Add a set of messages (FTL or HTML) for a specific locale or for HTML globally
|
|
497
|
+
addMessages(target, messages) {
|
|
498
|
+
if (target === "html") {
|
|
499
|
+
messages.forEach((msg) => this.htmlMessages.add(msg));
|
|
500
|
+
} else {
|
|
501
|
+
let ftlLocaleMessages = this.ftlMessages.get(target);
|
|
502
|
+
if (!ftlLocaleMessages) {
|
|
503
|
+
ftlLocaleMessages = /* @__PURE__ */ new Set();
|
|
504
|
+
this.ftlMessages.set(target, ftlLocaleMessages);
|
|
505
|
+
}
|
|
506
|
+
messages.forEach((msg) => ftlLocaleMessages.add(msg));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
validateMessages() {
|
|
510
|
+
this.htmlMessages.forEach((msg) => {
|
|
511
|
+
if (this.ignores.includes(msg))
|
|
512
|
+
return;
|
|
513
|
+
const missingLocales = [...this.ftlMessages.entries()].filter(([_, messages]) => !messages.has(msg)).map(([locale]) => locale);
|
|
514
|
+
if (missingLocales.length > 0)
|
|
515
|
+
logger.warn(`I10N id ${styleText.blue(msg)} missing in locale: ${missingLocales.join(", ")}`);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
getFTLMessages() {
|
|
519
|
+
const allMessages = /* @__PURE__ */ new Set();
|
|
520
|
+
this.ftlMessages.forEach((messages) => messages.forEach((msg) => allMessages.add(msg)));
|
|
521
|
+
return allMessages;
|
|
522
|
+
}
|
|
523
|
+
// Get all FTL messages for a specific locale
|
|
524
|
+
getFTLMessagesByLocale(locale) {
|
|
525
|
+
return this.ftlMessages.get(locale) || /* @__PURE__ */ new Set();
|
|
526
|
+
}
|
|
527
|
+
// Get all HTML messages
|
|
528
|
+
getHTMLMessages() {
|
|
529
|
+
return this.htmlMessages;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function processHTMLFile(content, namespace, allMessages, ignores, filePath) {
|
|
533
|
+
const foundMessages = /* @__PURE__ */ new Set();
|
|
534
|
+
const L10N_PATTERN = new RegExp(`(data-l10n-id)="((?!${namespace})\\S*)"`, "g");
|
|
535
|
+
const processed = content.replace(L10N_PATTERN, (match, attr, id) => {
|
|
536
|
+
foundMessages.add(id);
|
|
537
|
+
if (ignores.includes(id)) {
|
|
538
|
+
logger.debug(`Skipped ignored ID: ${styleText.blue(id)} in ${styleText.gray(filePath)}`);
|
|
539
|
+
return match;
|
|
540
|
+
}
|
|
541
|
+
if (!allMessages.has(id)) {
|
|
542
|
+
logger.warn(`I10N id ${styleText.blue(id)} in path ${styleText.gray(filePath)} does not exist in any locale, skip renaming it.`);
|
|
543
|
+
return match;
|
|
544
|
+
}
|
|
545
|
+
return `${attr}="${namespace}-${id}"`;
|
|
546
|
+
});
|
|
547
|
+
return { processedContent: processed, foundMessages: [...foundMessages] };
|
|
548
|
+
}
|
|
549
|
+
function generateFluentDts(messages) {
|
|
550
|
+
return `// Generated by zotero-plugin-scaffold
|
|
551
|
+
/* prettier-ignore */
|
|
552
|
+
/* eslint-disable */
|
|
553
|
+
// @ts-nocheck
|
|
554
|
+
export type FluentMessageId =
|
|
555
|
+
${Array.from(messages).sort().map((id) => ` | '${id}'`).join("\n")};
|
|
556
|
+
`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function is32BitNumber(n) {
|
|
560
|
+
return Number.isInteger(n) && n >= -2147483648 && n <= 2147483647;
|
|
561
|
+
}
|
|
562
|
+
|
|
362
563
|
class PrefsManager {
|
|
363
564
|
namespace;
|
|
364
565
|
prefs = {};
|
|
@@ -580,6 +781,62 @@ class PrefsManager {
|
|
|
580
781
|
return _prefs;
|
|
581
782
|
}
|
|
582
783
|
}
|
|
784
|
+
|
|
785
|
+
async function buildPrefs(dist, options) {
|
|
786
|
+
const { dts, prefixPrefKeys, prefix } = options;
|
|
787
|
+
if (!prefixPrefKeys && !dts)
|
|
788
|
+
return;
|
|
789
|
+
const prefsFilePath = join(dist, "addon", "prefs.js");
|
|
790
|
+
if (!existsSync(prefsFilePath))
|
|
791
|
+
return;
|
|
792
|
+
const prefsManager = new PrefsManager("pref");
|
|
793
|
+
await prefsManager.read(prefsFilePath);
|
|
794
|
+
const prefsWithPrefix = prefsManager.getPrefsWithPrefix(prefix);
|
|
795
|
+
const prefsWithoutPrefix = prefsManager.getPrefsWithoutPrefix(prefix);
|
|
796
|
+
const prefs = prefsManager.getPrefs();
|
|
797
|
+
Object.entries(prefs).forEach(([key, value]) => {
|
|
798
|
+
if (typeof value === "number") {
|
|
799
|
+
if (!is32BitNumber(value)) {
|
|
800
|
+
logger.warn(`Pref key '${styleText.blue(key)}' is a number, but is more than 4 bytes, which can be problematic on some OS.`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
if (dts) {
|
|
805
|
+
const dtsContent = renderPluginPrefsDts(prefsWithoutPrefix);
|
|
806
|
+
await outputFile(dts, dtsContent, "utf-8");
|
|
807
|
+
}
|
|
808
|
+
if (prefixPrefKeys) {
|
|
809
|
+
prefsManager.clearPrefs();
|
|
810
|
+
prefsManager.setPrefs(prefsWithPrefix);
|
|
811
|
+
await prefsManager.write(prefsFilePath);
|
|
812
|
+
}
|
|
813
|
+
if (prefixPrefKeys) {
|
|
814
|
+
const HTML_PREFERENCE_PATTERN = /preference="(\S*)"/g;
|
|
815
|
+
const xhtmlPaths = await glob(`${dist}/addon/**/*.xhtml`);
|
|
816
|
+
await Promise.all(xhtmlPaths.map(async (path) => {
|
|
817
|
+
let content = await readFile(path, "utf-8");
|
|
818
|
+
const matchs = [...content.matchAll(HTML_PREFERENCE_PATTERN)];
|
|
819
|
+
for (const match of matchs) {
|
|
820
|
+
const [matched, key] = match;
|
|
821
|
+
if (key.startsWith(prefix)) {
|
|
822
|
+
logger.debug(`Pref key '${styleText.blue(key)}' is already starts with '${prefix}', skip prefixing it.`);
|
|
823
|
+
continue;
|
|
824
|
+
} else if (key.startsWith("extensions.")) {
|
|
825
|
+
logger.warn(`Pref key '${styleText.blue(key)}' in ${styleText.gray(path)} starts with 'extensions.' but not '${styleText.blue(prefix)}', skip prefixing it.`);
|
|
826
|
+
continue;
|
|
827
|
+
} else if (!(key in prefsWithPrefix) && !(key in prefsWithoutPrefix)) {
|
|
828
|
+
logger.warn(`Pref key '${styleText.blue(key)}' in ${styleText.gray(path)} is not found in prefs.js, skip prefixing it.`);
|
|
829
|
+
continue;
|
|
830
|
+
} else {
|
|
831
|
+
const prefixed = `${prefix}.${key}`;
|
|
832
|
+
logger.debug(`Pref key '${styleText.blue(key)}' in ${styleText.gray(path)} is prefixed to ${styleText.blue(prefixed)}.`);
|
|
833
|
+
content = content.replace(matched, `preference="${prefixed}"`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
await outputFile(path, content, "utf-8");
|
|
837
|
+
}));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
583
840
|
function renderPluginPrefsDts(prefs) {
|
|
584
841
|
return `// Generated by zotero-plugin-scaffold
|
|
585
842
|
/* prettier-ignore */
|
|
@@ -599,16 +856,6 @@ declare namespace _ZoteroTypes {
|
|
|
599
856
|
`;
|
|
600
857
|
}
|
|
601
858
|
|
|
602
|
-
class Base {
|
|
603
|
-
ctx;
|
|
604
|
-
constructor(ctx) {
|
|
605
|
-
this.ctx = ctx;
|
|
606
|
-
}
|
|
607
|
-
get logger() {
|
|
608
|
-
return this.ctx.logger;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
859
|
class Build extends Base {
|
|
613
860
|
buildTime;
|
|
614
861
|
isPreRelease;
|
|
@@ -711,119 +958,11 @@ class Build extends Base {
|
|
|
711
958
|
}
|
|
712
959
|
async prepareLocaleFiles() {
|
|
713
960
|
const { dist, namespace, build } = this.ctx;
|
|
714
|
-
|
|
715
|
-
const FTL_MESSAGE_PATTERN = /^(?<message>[a-z]\S*)( *= *)(?<pattern>.*)$/gim;
|
|
716
|
-
const HTML_DATAI10NID_PATTERN = new RegExp(`(data-l10n-id)="((?!${namespace})\\S*)"`, "g");
|
|
717
|
-
const localePaths = await glob(`${dist}/addon/locale/*`, { onlyDirectories: true });
|
|
718
|
-
const localeNames = localePaths.map((locale) => basename(locale));
|
|
719
|
-
this.logger.debug(`Locale names:", ${localeNames}`);
|
|
720
|
-
const allMessages = /* @__PURE__ */ new Set();
|
|
721
|
-
const messagesByLocale = /* @__PURE__ */ new Map();
|
|
722
|
-
for (const localeName of localeNames) {
|
|
723
|
-
const localeMessages = /* @__PURE__ */ new Set();
|
|
724
|
-
const ftlPaths = await glob(`${dist}/addon/locale/${localeName}/**/*.ftl`);
|
|
725
|
-
await Promise.all(ftlPaths.map(async (ftlPath) => {
|
|
726
|
-
let ftlContent = await readFile(ftlPath, "utf-8");
|
|
727
|
-
const matches = [...ftlContent.matchAll(FTL_MESSAGE_PATTERN)];
|
|
728
|
-
for (const match of matches) {
|
|
729
|
-
const [matched, message, _pattern] = match;
|
|
730
|
-
if (message) {
|
|
731
|
-
localeMessages.add(message);
|
|
732
|
-
allMessages.add(message);
|
|
733
|
-
ftlContent = ftlContent.replace(new RegExp(`^${escapeRegExp(matched)}`, "gm"), `${namespace}-${matched}`);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
if (build.fluent.prefixFluentMessages)
|
|
737
|
-
await writeFile(ftlPath, ftlContent);
|
|
738
|
-
if (build.fluent.prefixLocaleFiles === true) {
|
|
739
|
-
await move(ftlPath, `${dirname(ftlPath)}/${namespace}-${basename(ftlPath)}`);
|
|
740
|
-
this.logger.debug(`FTL file '${ftlPath}' is renamed to '${namespace}-${basename(ftlPath)}'.`);
|
|
741
|
-
}
|
|
742
|
-
}));
|
|
743
|
-
messagesByLocale.set(localeName, localeMessages);
|
|
744
|
-
}
|
|
745
|
-
const messagesInHTML = /* @__PURE__ */ new Set();
|
|
746
|
-
const htmlPaths = await glob([
|
|
747
|
-
`${dist}/addon/**/*.xhtml`,
|
|
748
|
-
`${dist}/addon/**/*.html`
|
|
749
|
-
]);
|
|
750
|
-
await Promise.all(htmlPaths.map(async (htmlPath) => {
|
|
751
|
-
let htmlContent = await readFile(htmlPath, "utf-8");
|
|
752
|
-
const matches = [...htmlContent.matchAll(HTML_DATAI10NID_PATTERN)];
|
|
753
|
-
for (const match of matches) {
|
|
754
|
-
const [matched, attrKey, attrVal] = match;
|
|
755
|
-
if (ignores.includes(attrVal)) {
|
|
756
|
-
this.logger.debug(`HTML data-i10n-id ${attrVal} is in ignore list, skip to namespace`);
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
if (!allMessages.has(attrVal)) {
|
|
760
|
-
this.logger.warn(`HTML data-i10n-id '${styleText.blue(attrVal)}' in ${styleText.gray(htmlPath)} do not exist in any FTL message, skip to namespace it.`);
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
messagesInHTML.add(attrVal);
|
|
764
|
-
const namespacedAttr = `${namespace}-${attrVal}`;
|
|
765
|
-
htmlContent = htmlContent.replace(matched, `${attrKey}="${namespacedAttr}"`);
|
|
766
|
-
this.logger.debug(`HTML data-i10n-id '${styleText.blue(attrVal)}' in ${styleText.gray(htmlPath)} is namespaced to ${styleText.blue(namespacedAttr)}.`);
|
|
767
|
-
}
|
|
768
|
-
if (build.fluent.prefixFluentMessages)
|
|
769
|
-
await writeFile(htmlPath, htmlContent);
|
|
770
|
-
}));
|
|
771
|
-
messagesInHTML.forEach((messageInHTML) => {
|
|
772
|
-
if (ignores.includes(messageInHTML))
|
|
773
|
-
return;
|
|
774
|
-
const missingLocales = [...messagesByLocale.entries()].filter(([_, messages]) => !messages.has(messageInHTML)).map(([locale]) => locale);
|
|
775
|
-
if (missingLocales.length > 0) {
|
|
776
|
-
this.logger.warn(`HTML data-l10n-id '${styleText.blue(messageInHTML)}' is missing in locales: ${missingLocales.join(", ")}.`);
|
|
777
|
-
}
|
|
778
|
-
});
|
|
961
|
+
await buildLocale(dist, namespace, build.fluent);
|
|
779
962
|
}
|
|
780
963
|
async preparePrefs() {
|
|
781
|
-
const { dts, prefixPrefKeys, prefix } = this.ctx.build.prefs;
|
|
782
964
|
const { dist } = this.ctx;
|
|
783
|
-
|
|
784
|
-
return;
|
|
785
|
-
const prefsFilePath = join(dist, "addon", "prefs.js");
|
|
786
|
-
if (!existsSync(prefsFilePath))
|
|
787
|
-
return;
|
|
788
|
-
const prefsManager = new PrefsManager("pref");
|
|
789
|
-
await prefsManager.read(prefsFilePath);
|
|
790
|
-
const prefsWithPrefix = prefsManager.getPrefsWithPrefix(prefix);
|
|
791
|
-
const prefsWithoutPrefix = prefsManager.getPrefsWithoutPrefix(prefix);
|
|
792
|
-
if (dts) {
|
|
793
|
-
const dtsContent = renderPluginPrefsDts(prefsWithoutPrefix);
|
|
794
|
-
await outputFile(dts, dtsContent, "utf-8");
|
|
795
|
-
}
|
|
796
|
-
if (prefixPrefKeys) {
|
|
797
|
-
prefsManager.clearPrefs();
|
|
798
|
-
prefsManager.setPrefs(prefsWithPrefix);
|
|
799
|
-
await prefsManager.write(prefsFilePath);
|
|
800
|
-
}
|
|
801
|
-
if (prefixPrefKeys) {
|
|
802
|
-
const HTML_PREFERENCE_PATTERN = /preference="(\S*)"/g;
|
|
803
|
-
const xhtmlPaths = await glob(`${dist}/addon/**/*.xhtml`);
|
|
804
|
-
await Promise.all(xhtmlPaths.map(async (path) => {
|
|
805
|
-
let content = await readFile(path, "utf-8");
|
|
806
|
-
const matchs = [...content.matchAll(HTML_PREFERENCE_PATTERN)];
|
|
807
|
-
for (const match of matchs) {
|
|
808
|
-
const [matched, key] = match;
|
|
809
|
-
if (key.startsWith(prefix)) {
|
|
810
|
-
this.logger.debug(`Pref key '${styleText.blue(key)}' is already starts with '${prefix}', skip prefixing it.`);
|
|
811
|
-
continue;
|
|
812
|
-
} else if (key.startsWith("extensions.")) {
|
|
813
|
-
this.logger.warn(`Pref key '${styleText.blue(key)}' in ${styleText.gray(path)} starts with 'extensions.' but not '${styleText.blue(prefix)}', skip prefixing it.`);
|
|
814
|
-
continue;
|
|
815
|
-
} else if (!(key in prefsWithPrefix) && !(key in prefsWithoutPrefix)) {
|
|
816
|
-
this.logger.warn(`Pref key '${styleText.blue(key)}' in ${styleText.gray(path)} is not found in prefs.js, skip prefixing it.`);
|
|
817
|
-
continue;
|
|
818
|
-
} else {
|
|
819
|
-
const prefixed = `${prefix}.${key}`;
|
|
820
|
-
this.logger.debug(`Pref key '${styleText.blue(key)}' in ${styleText.gray(path)} is prefixed to ${styleText.blue(prefixed)}.`);
|
|
821
|
-
content = content.replace(matched, `preference="${prefixed}"`);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
await outputFile(path, content, "utf-8");
|
|
825
|
-
}));
|
|
826
|
-
}
|
|
965
|
+
await buildPrefs(dist, this.ctx.build.prefs);
|
|
827
966
|
}
|
|
828
967
|
esbuild() {
|
|
829
968
|
const { dist, build: { esbuildOptions } } = this.ctx;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zotero-plugin-scaffold",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "A scaffold for Zotero plugin development.",
|
|
6
6
|
"author": "northword",
|
|
7
7
|
"license": "AGPL-3.0-or-later",
|
|
@@ -63,20 +63,21 @@
|
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@
|
|
66
|
+
"@fluent/syntax": "^0.19.0",
|
|
67
|
+
"@swc/core": "^1.11.24",
|
|
67
68
|
"adm-zip": "^0.5.16",
|
|
68
69
|
"bumpp": "^10.1.0",
|
|
69
70
|
"c12": "^3.0.3",
|
|
70
71
|
"chokidar": "^4.0.3",
|
|
71
72
|
"commander": "^13.1.0",
|
|
72
|
-
"es-toolkit": "^1.
|
|
73
|
-
"esbuild": "^0.25.
|
|
73
|
+
"es-toolkit": "^1.36.0",
|
|
74
|
+
"esbuild": "^0.25.3",
|
|
74
75
|
"fs-extra": "^11.3.0",
|
|
75
76
|
"hookable": "^5.5.3",
|
|
76
|
-
"octokit": "^4.1.
|
|
77
|
+
"octokit": "^4.1.3",
|
|
77
78
|
"std-env": "^3.9.0",
|
|
78
79
|
"tiny-update-notifier": "^2.0.0",
|
|
79
|
-
"tinyglobby": "^0.2.
|
|
80
|
+
"tinyglobby": "^0.2.13",
|
|
80
81
|
"xvfb-ts": "^1.1.0"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|