wuchale 0.22.0 → 0.22.1
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/adapter-vanilla/transformer.js +3 -0
- package/dist/adapters.d.ts +8 -0
- package/dist/ai/index.js +21 -2
- package/dist/bundlers/vite.d.ts +1 -1
- package/dist/bundlers/vite.js +14 -20
- package/dist/compile.js +13 -13
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +5 -1
- package/dist/handler/files.d.ts +1 -1
- package/dist/handler/files.js +24 -12
- package/dist/handler/index.js +3 -1
- package/dist/hub.d.ts +2 -1
- package/dist/hub.js +25 -12
- package/dist/pofile.d.ts +1 -1
- package/dist/pofile.js +63 -67
- package/dist/runtime.js +3 -8
- package/dist/storage.d.ts +2 -0
- package/package.json +1 -1
|
@@ -417,10 +417,13 @@ export class Transformer {
|
|
|
417
417
|
this.heuristciDetails.declaring = 'variable';
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
|
+
this.heuristciDetails.leftSide = true;
|
|
420
421
|
const msgs = this.visit(node.id);
|
|
422
|
+
this.heuristciDetails.leftSide = false;
|
|
421
423
|
if (topLevel && this.heuristciDetails.declaring === 'variable' && init.type === 'CallExpression') {
|
|
422
424
|
this.heuristciDetails.topLevelCall = this.getCalleeName(init.callee);
|
|
423
425
|
}
|
|
426
|
+
delete this.heuristciDetails.leftSide;
|
|
424
427
|
return [...msgs, ...this.visit(node.init)];
|
|
425
428
|
});
|
|
426
429
|
visitExpressionStatement = (node) => this.withUpdateTLDetails(topLevel => {
|
package/dist/adapters.d.ts
CHANGED
|
@@ -9,11 +9,19 @@ export type HeuristicDetailsBase = {
|
|
|
9
9
|
export type ScriptDeclType = 'variable' | 'function' | 'class' | 'expression';
|
|
10
10
|
export type HeuristicDetails = HeuristicDetailsBase & {
|
|
11
11
|
file: string;
|
|
12
|
+
/** the type of the top level declaration */
|
|
12
13
|
declaring?: ScriptDeclType;
|
|
14
|
+
/** in assignments, whether the string is on the left side as destructuring default */
|
|
15
|
+
leftSide?: boolean;
|
|
16
|
+
/** the name of the function being defined, '' for arrow or null for global */
|
|
13
17
|
funcName?: string | null;
|
|
18
|
+
/** whether the function being defined is nested inside another, null for no function */
|
|
14
19
|
funcIsNested?: boolean;
|
|
20
|
+
/** whether inside a script file/<script> instead of an expression inside markup */
|
|
15
21
|
insideProgram: boolean;
|
|
22
|
+
/** the name of the call at the top level */
|
|
16
23
|
topLevelCall?: string;
|
|
24
|
+
/** the name of the nearest call (for arguments) */
|
|
17
25
|
call?: string;
|
|
18
26
|
};
|
|
19
27
|
export type MessageType = 'message' | 'url';
|
package/dist/ai/index.js
CHANGED
|
@@ -91,16 +91,35 @@ export default class AIQueue {
|
|
|
91
91
|
const sourceComp = id.map(i => compileTranslation(i, ''));
|
|
92
92
|
for (const loc of batch.targetLocales) {
|
|
93
93
|
const translation = outItem[loc];
|
|
94
|
-
if (translation
|
|
94
|
+
if (translation === undefined) {
|
|
95
95
|
unTranslated.push(item);
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
98
|
+
if (id.length > 1) {
|
|
99
|
+
// plural
|
|
100
|
+
if (translation.length === 0) {
|
|
101
|
+
// TODO: pass pluralRule and check nplurals
|
|
102
|
+
unTranslated.push(item);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
item.translations.set(loc, translation);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (translation.length !== id.length) {
|
|
109
|
+
unTranslated.push(item);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
let equivalent = true;
|
|
98
113
|
for (const [i, sou] of sourceComp.entries()) {
|
|
99
114
|
if (!isEquivalent(sou, compileTranslation(translation[i], ''))) {
|
|
100
|
-
|
|
115
|
+
equivalent = false;
|
|
101
116
|
break;
|
|
102
117
|
}
|
|
103
118
|
}
|
|
119
|
+
if (!equivalent) {
|
|
120
|
+
unTranslated.push(item);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
104
123
|
item.translations.set(loc, translation);
|
|
105
124
|
}
|
|
106
125
|
}
|
package/dist/bundlers/vite.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare function toViteError(err:
|
|
1
|
+
export declare function toViteError(err: any, adapterKey: string, filename: string): Error;
|
|
2
2
|
export declare function trimViteQueries(id: string): string;
|
|
3
3
|
type HotUpdateCtx = {
|
|
4
4
|
file: string;
|
package/dist/bundlers/vite.js
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import { dirname } from 'node:path';
|
|
2
|
-
import { inspect } from 'node:util';
|
|
3
2
|
import { getConfig } from 'wuchale';
|
|
4
3
|
import { Hub, pluginName } from '../hub.js';
|
|
5
4
|
export function toViteError(err, adapterKey, filename) {
|
|
6
5
|
const prefix = `${adapterKey}: transform failed for ${filename}`;
|
|
7
6
|
// Ensure we always throw an Error instance with a non-empty message so build tools (e.g. Vite)
|
|
8
7
|
// don't end up printing only a generic "error during build:" line.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const frameText = frame ? `\n\n${frame}` : '';
|
|
15
|
-
err.message = `${prefix}${details}${frameText}`;
|
|
16
|
-
}
|
|
17
|
-
// Preserve useful metadata that some tooling expects.
|
|
18
|
-
if (anyErr.id == null)
|
|
19
|
-
anyErr.id = filename;
|
|
20
|
-
if (anyErr.loc == null && anyErr.start?.line != null && anyErr.start?.column != null) {
|
|
21
|
-
anyErr.loc = { file: filename, line: anyErr.start.line, column: anyErr.start.column };
|
|
22
|
-
}
|
|
23
|
-
throw err;
|
|
8
|
+
const frame = typeof err.frame === 'string' ? err.frame : undefined;
|
|
9
|
+
if (!err.message || !err.message.startsWith(prefix)) {
|
|
10
|
+
const details = err.message ? `\n${err.message}` : '';
|
|
11
|
+
const frameText = frame ? `\n\n${frame}` : '';
|
|
12
|
+
err.message = `${prefix}${details}${frameText}`;
|
|
24
13
|
}
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
// Preserve useful metadata that some tooling expects.
|
|
15
|
+
if (err.id == null)
|
|
16
|
+
err.id = filename;
|
|
17
|
+
if (err.loc == null && err.start?.line != null && err.start?.column != null) {
|
|
18
|
+
err.loc = { file: filename, line: err.start.line, column: err.start.column };
|
|
19
|
+
}
|
|
20
|
+
return err;
|
|
27
21
|
}
|
|
28
22
|
export function trimViteQueries(id) {
|
|
29
23
|
let queryIndex = id.indexOf('?v=');
|
|
@@ -37,7 +31,7 @@ export function trimViteQueries(id) {
|
|
|
37
31
|
return id;
|
|
38
32
|
}
|
|
39
33
|
export const wuchale = (configPath, hmrDelayThreshold = 1000) => {
|
|
40
|
-
const hub = new Hub(() => getConfig(configPath), dirname(configPath ?? '.'), hmrDelayThreshold);
|
|
34
|
+
const hub = new Hub(() => getConfig(configPath), dirname(configPath ?? '.'), hmrDelayThreshold, undefined, toViteError);
|
|
41
35
|
return {
|
|
42
36
|
name: pluginName,
|
|
43
37
|
async configResolved(config) {
|
|
@@ -54,7 +48,7 @@ export const wuchale = (configPath, hmrDelayThreshold = 1000) => {
|
|
|
54
48
|
ctx.server.moduleGraph.invalidateModule(module, invalidatedModules, ctx.timestamp, false);
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
|
-
if (!changeInfo.sourceTriggered) {
|
|
51
|
+
if (!changeInfo.sourceTriggered && changeInfo.invalidate.size > 0) {
|
|
58
52
|
ctx.server.ws.send({ type: 'full-reload' });
|
|
59
53
|
return [];
|
|
60
54
|
}
|
package/dist/compile.js
CHANGED
|
@@ -77,7 +77,7 @@ function compile(msgStr, start = 0, parentTag = null) {
|
|
|
77
77
|
if (type === CLOSE) {
|
|
78
78
|
if (currentOpenTag != null) {
|
|
79
79
|
if (currentOpenTag != n) {
|
|
80
|
-
|
|
80
|
+
return [compiled, 0, 'Closing a different tag'];
|
|
81
81
|
}
|
|
82
82
|
currentOpenTag = null;
|
|
83
83
|
}
|
|
@@ -85,7 +85,7 @@ function compile(msgStr, start = 0, parentTag = null) {
|
|
|
85
85
|
break;
|
|
86
86
|
}
|
|
87
87
|
else {
|
|
88
|
-
|
|
88
|
+
return [compiled, 0, 'Closing a different tag'];
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
else if (type === SELF_CLOSE) {
|
|
@@ -100,24 +100,24 @@ function compile(msgStr, start = 0, parentTag = null) {
|
|
|
100
100
|
if (curTxt) {
|
|
101
101
|
compiled.push(curTxt);
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
if (currentOpenTag !== null) {
|
|
104
|
+
return [compiled, 0, 'Unexpected end'];
|
|
105
|
+
}
|
|
106
|
+
return [compiled, i, null];
|
|
104
107
|
}
|
|
105
108
|
export function compileTranslation(msgStr, fallback) {
|
|
106
109
|
if (!msgStr) {
|
|
107
110
|
return fallback;
|
|
108
111
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return compiled[0];
|
|
113
|
-
}
|
|
114
|
-
return compiled;
|
|
115
|
-
}
|
|
116
|
-
catch (err) {
|
|
117
|
-
console.error(err);
|
|
118
|
-
console.error(msgStr);
|
|
112
|
+
const [compiled, , err] = compile(msgStr);
|
|
113
|
+
if (err !== null) {
|
|
114
|
+
console.error('Compile error:', err, ':', msgStr);
|
|
119
115
|
return fallback;
|
|
120
116
|
}
|
|
117
|
+
if (compiled.length === 1 && typeof compiled[0] === 'string') {
|
|
118
|
+
return compiled[0];
|
|
119
|
+
}
|
|
120
|
+
return compiled;
|
|
121
121
|
}
|
|
122
122
|
export function isEquivalent(source, translation) {
|
|
123
123
|
const sourceStr = typeof source === 'string';
|
package/dist/fs.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type FS = {
|
|
|
3
3
|
write(file: string, content: string): void | Promise<void>;
|
|
4
4
|
mkdir(path: string): void | Promise<void>;
|
|
5
5
|
exists(path: string): boolean | Promise<boolean>;
|
|
6
|
+
unlink(path: string): void | Promise<void>;
|
|
6
7
|
};
|
|
7
8
|
export declare const defaultFS: FS;
|
|
8
9
|
export declare const readOnlyFS: FS;
|
package/dist/fs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, statfs, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, statfs, unlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
export const defaultFS = {
|
|
3
3
|
async read(file) {
|
|
4
4
|
return await readFile(file, 'utf-8');
|
|
@@ -21,9 +21,13 @@ export const defaultFS = {
|
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
|
+
async unlink(path) {
|
|
25
|
+
await unlink(path);
|
|
26
|
+
},
|
|
24
27
|
};
|
|
25
28
|
export const readOnlyFS = {
|
|
26
29
|
...defaultFS,
|
|
27
30
|
write: () => { },
|
|
28
31
|
mkdir: () => { },
|
|
32
|
+
unlink: () => { },
|
|
29
33
|
};
|
package/dist/handler/files.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type ManifestEntryObj = {
|
|
|
12
12
|
export type ManifestEntry = string | string[] | ManifestEntryObj | null;
|
|
13
13
|
export declare const objKeyLocale: (locale: string) => string;
|
|
14
14
|
export declare function normalizeSep(path: string): string;
|
|
15
|
-
export declare function globConfToArgs(conf: GlobConf,
|
|
15
|
+
export declare function globConfToArgs(conf: GlobConf, root: string, localesDir: string, outDir?: string): [string[], {
|
|
16
16
|
ignore: string[];
|
|
17
17
|
}];
|
|
18
18
|
export declare class Files {
|
package/dist/handler/files.js
CHANGED
|
@@ -11,10 +11,10 @@ export function normalizeSep(path) {
|
|
|
11
11
|
}
|
|
12
12
|
return path.replaceAll('\\', '/');
|
|
13
13
|
}
|
|
14
|
-
export function globConfToArgs(conf,
|
|
14
|
+
export function globConfToArgs(conf, root, localesDir, outDir) {
|
|
15
15
|
let patterns = [];
|
|
16
16
|
// ignore generated files
|
|
17
|
-
const options = { ignore: [localesDir], cwd };
|
|
17
|
+
const options = { ignore: [`${localesDir}/**/*`], cwd: root };
|
|
18
18
|
if (outDir) {
|
|
19
19
|
options.ignore.push(outDir);
|
|
20
20
|
}
|
|
@@ -51,23 +51,23 @@ export class Files {
|
|
|
51
51
|
proxySyncPath;
|
|
52
52
|
#urlManifestFname;
|
|
53
53
|
#urlsFname;
|
|
54
|
-
#
|
|
54
|
+
#localesDirAbs;
|
|
55
55
|
#projectRoot;
|
|
56
56
|
constructor(adapter, key, localesDir, fs, root) {
|
|
57
57
|
this.key = key;
|
|
58
58
|
this.#adapter = adapter;
|
|
59
|
-
this.#
|
|
59
|
+
this.#localesDirAbs = resolve(root, localesDir);
|
|
60
60
|
this.#fs = fs;
|
|
61
61
|
this.#projectRoot = root;
|
|
62
62
|
}
|
|
63
63
|
getLoaderPaths() {
|
|
64
|
-
const loaderPathHead = resolve(this.#
|
|
64
|
+
const loaderPathHead = resolve(this.#localesDirAbs, `${this.key}.loader`);
|
|
65
65
|
const paths = [];
|
|
66
66
|
for (const ext of this.#adapter.loaderExts) {
|
|
67
67
|
const pathClient = loaderPathHead + ext;
|
|
68
68
|
const same = { client: pathClient, server: pathClient };
|
|
69
69
|
const diff = { client: pathClient, server: loaderPathHead + '.server' + ext };
|
|
70
|
-
if (this.#adapter.defaultLoaderPath
|
|
70
|
+
if (this.#adapter.defaultLoaderPath === null) {
|
|
71
71
|
paths.push(diff, same);
|
|
72
72
|
}
|
|
73
73
|
else if (typeof this.#adapter.defaultLoaderPath === 'string') {
|
|
@@ -95,6 +95,18 @@ export class Files {
|
|
|
95
95
|
}
|
|
96
96
|
return path;
|
|
97
97
|
}
|
|
98
|
+
if (this.#adapter.defaultLoaderPath === null) {
|
|
99
|
+
const loaderForms = paths
|
|
100
|
+
.map(p => {
|
|
101
|
+
let f = ` ${relative(this.#projectRoot, p.client)}`;
|
|
102
|
+
if (p.server !== p.client) {
|
|
103
|
+
f += ` (and ${relative(this.#projectRoot, p.server)})`;
|
|
104
|
+
}
|
|
105
|
+
return f;
|
|
106
|
+
})
|
|
107
|
+
.join('\n');
|
|
108
|
+
throw new Error(`Custom loader specified for adapter '${this.key}' but no loader file exists in one of the forms:\n${loaderForms}`);
|
|
109
|
+
}
|
|
98
110
|
return paths[0];
|
|
99
111
|
}
|
|
100
112
|
#proxyFileName(sync = false) {
|
|
@@ -106,14 +118,14 @@ export class Files {
|
|
|
106
118
|
}
|
|
107
119
|
async #initPaths() {
|
|
108
120
|
this.loaderPath = await this.getLoaderPath();
|
|
109
|
-
this.proxyPath = resolve(this.#
|
|
110
|
-
this.proxySyncPath = resolve(this.#
|
|
111
|
-
this.#urlManifestFname = resolve(this.#
|
|
112
|
-
this.#urlsFname = resolve(this.#
|
|
121
|
+
this.proxyPath = resolve(this.#localesDirAbs, generatedDir, this.#proxyFileName());
|
|
122
|
+
this.proxySyncPath = resolve(this.#localesDirAbs, generatedDir, this.#proxyFileName(true));
|
|
123
|
+
this.#urlManifestFname = resolve(this.#localesDirAbs, generatedDir, `${this.key}.urls.js`);
|
|
124
|
+
this.#urlsFname = resolve(this.#localesDirAbs, `${this.key}.url.js`);
|
|
113
125
|
}
|
|
114
126
|
getCompiledFilePath(loc, id) {
|
|
115
127
|
const ownerKey = this.ownerKey;
|
|
116
|
-
return resolve(this.#
|
|
128
|
+
return resolve(this.#localesDirAbs, generatedDir, `${ownerKey}.${id ?? ownerKey}.${loc}.compiled.js`);
|
|
117
129
|
}
|
|
118
130
|
getImportPath(filename, importer) {
|
|
119
131
|
const relTo = importer ? resolve(this.#projectRoot, importer) : filename;
|
|
@@ -211,7 +223,7 @@ export class Files {
|
|
|
211
223
|
};
|
|
212
224
|
getManifestFilePath(id) {
|
|
213
225
|
const ownerKey = this.ownerKey;
|
|
214
|
-
return resolve(this.#
|
|
226
|
+
return resolve(this.#localesDirAbs, generatedDir, `${ownerKey}.${id ?? ownerKey}.manifest.js`);
|
|
215
227
|
}
|
|
216
228
|
writeManifest = async (keys, id) => {
|
|
217
229
|
const content = `/** @type {(string | {text: string | string[], context?: string, isUrl?: boolean} | null)[]} */\n` +
|
package/dist/handler/index.js
CHANGED
|
@@ -85,7 +85,9 @@ export class AdapterHandler {
|
|
|
85
85
|
await this.sharedState.save();
|
|
86
86
|
};
|
|
87
87
|
compile = async (hmrVersion = -1) => {
|
|
88
|
-
|
|
88
|
+
// for proper fallback
|
|
89
|
+
const localesOrdered = [this.sourceLocale, ...this.#config.locales.filter(l => l !== this.sourceLocale)];
|
|
90
|
+
await Promise.all(localesOrdered.map(loc => this.#compileForLocale(loc, hmrVersion)));
|
|
89
91
|
await this.#writeManifests();
|
|
90
92
|
};
|
|
91
93
|
#buildManifest = (indices) => {
|
package/dist/hub.d.ts
CHANGED
|
@@ -43,9 +43,10 @@ type CheckResult = {
|
|
|
43
43
|
errors: CheckErrorItem[];
|
|
44
44
|
syncs: string[];
|
|
45
45
|
};
|
|
46
|
+
type TransformErrFormatter = (e: Error, adapterKey: string, filename: string) => Error;
|
|
46
47
|
export declare class Hub {
|
|
47
48
|
#private;
|
|
48
|
-
constructor(loadConfig: ConfigLoader, root: string, hmrDelayThreshold?: number, fs?: FS);
|
|
49
|
+
constructor(loadConfig: ConfigLoader, root: string, hmrDelayThreshold?: number, fs?: FS, formatTransformErr?: TransformErrFormatter);
|
|
49
50
|
init: (mode: Mode) => Promise<void>;
|
|
50
51
|
onFileChange: (file: string, read: () => string | Promise<string>) => Promise<FileChangeInfo | undefined>;
|
|
51
52
|
transform: (code: string, filePath: string, forServer?: boolean) => ReturnType<AdapterHandler["transform"]>;
|
package/dist/hub.js
CHANGED
|
@@ -6,7 +6,7 @@ import { watch as watchFS } from 'chokidar';
|
|
|
6
6
|
import {} from 'picomatch';
|
|
7
7
|
import { glob } from 'tinyglobby';
|
|
8
8
|
import { compileTranslation, isEquivalent } from './compile.js';
|
|
9
|
-
import { defaultFS
|
|
9
|
+
import { defaultFS } from './fs.js';
|
|
10
10
|
import { dataFileName, generatedDir, globConfToArgs, normalizeSep } from './handler/files.js';
|
|
11
11
|
import { AdapterHandler } from './handler/index.js';
|
|
12
12
|
import { SharedState } from './handler/state.js';
|
|
@@ -33,20 +33,23 @@ export class Hub {
|
|
|
33
33
|
#log;
|
|
34
34
|
#mode;
|
|
35
35
|
#loadConfig;
|
|
36
|
+
#formatTransformErr = e => e;
|
|
36
37
|
#hmrVersion = -1;
|
|
37
38
|
#hmrDelayThreshold;
|
|
38
39
|
#lastSourceTriggeredCatalogWrite = 0;
|
|
39
|
-
constructor(loadConfig, root, hmrDelayThreshold = 1000, fs = defaultFS) {
|
|
40
|
+
constructor(loadConfig, root, hmrDelayThreshold = 1000, fs = defaultFS, formatTransformErr) {
|
|
40
41
|
this.#loadConfig = loadConfig;
|
|
41
42
|
this.#fs = fs;
|
|
42
43
|
this.#projectRoot = root;
|
|
43
44
|
// threshold to consider po file change is manual edit instead of a sideeffect of editing code
|
|
44
45
|
this.#hmrDelayThreshold = hmrDelayThreshold;
|
|
46
|
+
this.#formatTransformErr = formatTransformErr ?? this.#formatTransformErr;
|
|
45
47
|
}
|
|
46
48
|
async #initGenDirWithData() {
|
|
47
|
-
|
|
49
|
+
const localesDirAbs = resolve(this.#projectRoot, this.#config.localesDir);
|
|
50
|
+
await this.#fs.mkdir(resolve(localesDirAbs, generatedDir));
|
|
48
51
|
// data file
|
|
49
|
-
await this.#fs.write(resolve(
|
|
52
|
+
await this.#fs.write(resolve(localesDirAbs, dataFileName), [
|
|
50
53
|
`/** @typedef {('${this.#config.locales.join("'|'")}')} Locale */`,
|
|
51
54
|
`/** @type {Locale[]} */`,
|
|
52
55
|
`export const locales = ['${this.#config.locales.join("','")}']`,
|
|
@@ -105,7 +108,8 @@ export class Hub {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
|
-
|
|
111
|
+
const confUpdateFileAbs = resolve(this.#projectRoot, this.#config.localesDir, generatedDir, confUpdateName);
|
|
112
|
+
this.#confUpdateFile = normalizeSep(confUpdateFileAbs);
|
|
109
113
|
await this.#fs.write(this.#confUpdateFile, '{}'); // only watch changes so prepare first
|
|
110
114
|
};
|
|
111
115
|
#getSharedState = (adapter, key, sourceLocale, fileMatches) => {
|
|
@@ -114,10 +118,8 @@ export class Hub {
|
|
|
114
118
|
root: this.#projectRoot,
|
|
115
119
|
sourceLocale: sourceLocale,
|
|
116
120
|
haveUrl: adapter.url != null,
|
|
121
|
+
fs: this.#fs,
|
|
117
122
|
});
|
|
118
|
-
if (this.#fs === readOnlyFS) {
|
|
119
|
-
storage.save = async () => { }; // disable writes
|
|
120
|
-
}
|
|
121
123
|
let sharedState = this.#sharedStates.get(storage.key);
|
|
122
124
|
if (sharedState == null) {
|
|
123
125
|
sharedState = new SharedState(storage, key, sourceLocale);
|
|
@@ -132,6 +134,7 @@ export class Hub {
|
|
|
132
134
|
return sharedState;
|
|
133
135
|
};
|
|
134
136
|
onFileChange = async (file, read) => {
|
|
137
|
+
file = normalizeSep(file); // just to be sure
|
|
135
138
|
if (this.#confUpdateFile === file) {
|
|
136
139
|
const update = JSON.parse(await read());
|
|
137
140
|
this.#log.info(`${logPrefix} config update received: ${update}`);
|
|
@@ -184,7 +187,7 @@ export class Hub {
|
|
|
184
187
|
if (this.#mode === 'dev' && !this.#config.hmr) {
|
|
185
188
|
return [{}, false];
|
|
186
189
|
}
|
|
187
|
-
const filename = relative(this.#projectRoot, filePath);
|
|
190
|
+
const filename = normalizeSep(relative(this.#projectRoot, filePath));
|
|
188
191
|
let output = null;
|
|
189
192
|
let lastAdapterKey = null;
|
|
190
193
|
for (const adapter of this.#handlers.values()) {
|
|
@@ -192,7 +195,12 @@ export class Hub {
|
|
|
192
195
|
if (lastAdapterKey != null) {
|
|
193
196
|
throw new Error(`${logPrefix} ${filename} matches both adapters ${lastAdapterKey} and ${adapter.key}`);
|
|
194
197
|
}
|
|
195
|
-
|
|
198
|
+
try {
|
|
199
|
+
output = await adapter.transform(code, filename, this.#hmrVersion, forServer);
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
throw this.#formatTransformErr(e, adapter.key, filename);
|
|
203
|
+
}
|
|
196
204
|
lastAdapterKey = adapter.key;
|
|
197
205
|
}
|
|
198
206
|
}
|
|
@@ -240,7 +248,8 @@ export class Hub {
|
|
|
240
248
|
}
|
|
241
249
|
}
|
|
242
250
|
if (updated) {
|
|
243
|
-
await handler.
|
|
251
|
+
await handler.saveStorage();
|
|
252
|
+
await handler.compile();
|
|
244
253
|
}
|
|
245
254
|
return updated;
|
|
246
255
|
}
|
|
@@ -248,7 +257,11 @@ export class Hub {
|
|
|
248
257
|
!watch && this.#log.info('Extracting...');
|
|
249
258
|
const handlers = Array.from(this.#handlers.values());
|
|
250
259
|
// owner adapter handlers should run last for cleanup
|
|
251
|
-
handlers.sort(
|
|
260
|
+
handlers.sort((a, b) => {
|
|
261
|
+
const aOwner = a.sharedState.ownerKey === a.key;
|
|
262
|
+
const bOwner = b.sharedState.ownerKey === b.key;
|
|
263
|
+
return aOwner === bOwner ? 0 : aOwner ? 1 : -1;
|
|
264
|
+
});
|
|
252
265
|
// separate loop to make sure that all otherFileMatchers are collected
|
|
253
266
|
for (const handler of handlers) {
|
|
254
267
|
await this.#directVisitHandler(handler, clean, sync);
|
package/dist/pofile.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PO from 'pofile';
|
|
2
2
|
import { type PluralRule, type SaveData, type StorageFactory, type StorageFactoryOpts } from './storage.js';
|
|
3
|
-
type POItem = InstanceType<typeof PO.Item>;
|
|
3
|
+
export type POItem = InstanceType<typeof PO.Item>;
|
|
4
4
|
export type POFileOptions = {
|
|
5
5
|
dir: string;
|
|
6
6
|
separateUrls: boolean;
|
package/dist/pofile.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isAbsolute, resolve } from 'node:path';
|
|
1
|
+
import { resolve } from 'node:path';
|
|
3
2
|
import PO from 'pofile';
|
|
4
3
|
import { getKey } from './adapters.js';
|
|
5
4
|
import { deepMergeObjects } from './config.js';
|
|
6
5
|
import { itemIsObsolete, itemIsUrl, } from './storage.js';
|
|
7
6
|
const urlAdapterFlagPrefix = 'url:';
|
|
7
|
+
function join(parts, sep) {
|
|
8
|
+
return parts.map(s => s.replaceAll('\\', '\\\\').replaceAll(sep, `\\${sep}`)).join(sep);
|
|
9
|
+
}
|
|
10
|
+
function split(str, sep, count) {
|
|
11
|
+
return str
|
|
12
|
+
.split(new RegExp(`(?<!\\\\)${sep}`), count)
|
|
13
|
+
.map(s => s.replaceAll(`\\${sep}`, sep).replaceAll('\\\\', '\\'));
|
|
14
|
+
}
|
|
8
15
|
function itemToPOItem(item, locale, sourceLocale) {
|
|
9
16
|
const poi = new PO.Item();
|
|
10
17
|
const id = item.translations.get(sourceLocale);
|
|
@@ -23,9 +30,9 @@ function itemToPOItem(item, locale, sourceLocale) {
|
|
|
23
30
|
comm.push(frEntry.link);
|
|
24
31
|
}
|
|
25
32
|
for (const [i, ph] of frEntry.placeholders) {
|
|
26
|
-
comm.push(
|
|
33
|
+
comm.push(join([String(i), ph], ': '));
|
|
27
34
|
}
|
|
28
|
-
return
|
|
35
|
+
return join(comm, '; ');
|
|
29
36
|
}))
|
|
30
37
|
.filter(c => c !== null);
|
|
31
38
|
const additionals = item.additionals ?? new Map();
|
|
@@ -37,15 +44,7 @@ function itemToPOItem(item, locale, sourceLocale) {
|
|
|
37
44
|
poi.obsolete = itemIsObsolete(item);
|
|
38
45
|
return poi;
|
|
39
46
|
}
|
|
40
|
-
function getItemId(poi) {
|
|
41
|
-
const msgid = [poi.msgid];
|
|
42
|
-
if (poi.msgid_plural) {
|
|
43
|
-
msgid.push(poi.msgid_plural);
|
|
44
|
-
}
|
|
45
|
-
return msgid;
|
|
46
|
-
}
|
|
47
47
|
function poitemToItemCommons(poi) {
|
|
48
|
-
const msgid = getItemId(poi);
|
|
49
48
|
const references = [];
|
|
50
49
|
let lastRef = { file: '', refs: [] };
|
|
51
50
|
const urlAdapters = [];
|
|
@@ -65,7 +64,7 @@ function poitemToItemCommons(poi) {
|
|
|
65
64
|
continue;
|
|
66
65
|
}
|
|
67
66
|
const refEnt = { placeholders: [] };
|
|
68
|
-
const commSp =
|
|
67
|
+
const commSp = split(comm, '; ');
|
|
69
68
|
let phStart = 0;
|
|
70
69
|
if (urlAdapters.length) {
|
|
71
70
|
// url
|
|
@@ -73,19 +72,51 @@ function poitemToItemCommons(poi) {
|
|
|
73
72
|
phStart++;
|
|
74
73
|
}
|
|
75
74
|
for (const c of commSp.slice(phStart)) {
|
|
76
|
-
const [i, ph] =
|
|
75
|
+
const [i, ph] = split(c, ': ', 2);
|
|
77
76
|
refEnt.placeholders.push([Number(i), ph]);
|
|
78
77
|
}
|
|
79
78
|
lastRef.refs.push(refEnt);
|
|
80
79
|
}
|
|
81
80
|
return {
|
|
82
|
-
id: msgid,
|
|
83
81
|
translations: new Map(),
|
|
84
82
|
context: poi.msgctxt,
|
|
85
83
|
references,
|
|
86
84
|
urlAdapters,
|
|
87
85
|
};
|
|
88
86
|
}
|
|
87
|
+
function getItemId(poItem) {
|
|
88
|
+
const id = [poItem.msgid];
|
|
89
|
+
if (poItem.msgid_plural) {
|
|
90
|
+
id.push(poItem.msgid_plural);
|
|
91
|
+
}
|
|
92
|
+
return id;
|
|
93
|
+
}
|
|
94
|
+
function poitemsToItems(poItems, locales, sourceLocale) {
|
|
95
|
+
// then merge them
|
|
96
|
+
const items = [];
|
|
97
|
+
for (const poIs of poItems) {
|
|
98
|
+
const basePoOtem = poIs.values().next().value;
|
|
99
|
+
const item = poitemToItemCommons(basePoOtem);
|
|
100
|
+
const additionals = new Map();
|
|
101
|
+
for (const loc of locales) {
|
|
102
|
+
const poi = poIs.get(loc);
|
|
103
|
+
item.translations.set(loc, poi?.msgstr ?? (loc === sourceLocale ? getItemId(basePoOtem) : []));
|
|
104
|
+
const add = {
|
|
105
|
+
comments: poi?.comments ?? [],
|
|
106
|
+
flags: {},
|
|
107
|
+
};
|
|
108
|
+
for (const [k, v] of Object.entries(poi?.flags ?? {})) {
|
|
109
|
+
if (!k.startsWith(urlAdapterFlagPrefix)) {
|
|
110
|
+
add.flags[k] = v;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
additionals.set(loc, add);
|
|
114
|
+
}
|
|
115
|
+
item.additionals = additionals;
|
|
116
|
+
items.push(item);
|
|
117
|
+
}
|
|
118
|
+
return items;
|
|
119
|
+
}
|
|
89
120
|
export const defaultOpts = {
|
|
90
121
|
dir: 'src/locales',
|
|
91
122
|
separateUrls: true,
|
|
@@ -96,11 +127,9 @@ export class POFile {
|
|
|
96
127
|
filesByLoc = new Map(); // main and url
|
|
97
128
|
files = [];
|
|
98
129
|
constructor(opts) {
|
|
99
|
-
this.key = opts.dir;
|
|
100
130
|
this.opts = opts;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
131
|
+
opts.dir = resolve(opts.root, opts.dir);
|
|
132
|
+
this.key = opts.dir;
|
|
104
133
|
for (const locale of opts.locales) {
|
|
105
134
|
const locFiles = [resolve(opts.dir, `${locale}.po`), resolve(opts.dir, `${locale}.url.po`)];
|
|
106
135
|
this.filesByLoc.set(locale, locFiles);
|
|
@@ -110,16 +139,7 @@ export class POFile {
|
|
|
110
139
|
async loadRaw(locale, url) {
|
|
111
140
|
const filename = this.filesByLoc.get(locale)[Number(url)];
|
|
112
141
|
try {
|
|
113
|
-
return await
|
|
114
|
-
PO.load(filename, (err, po) => {
|
|
115
|
-
if (err) {
|
|
116
|
-
rej(err);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
res(po);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
});
|
|
142
|
+
return PO.parse(await this.opts.fs.read(filename));
|
|
123
143
|
}
|
|
124
144
|
catch (err) {
|
|
125
145
|
if (err.code !== 'ENOENT') {
|
|
@@ -156,46 +176,24 @@ export class POFile {
|
|
|
156
176
|
poItems.get(key)?.set(locale, poItem);
|
|
157
177
|
}
|
|
158
178
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const additionals = new Map();
|
|
164
|
-
for (const loc of this.opts.locales) {
|
|
165
|
-
const poi = poIs.get(loc);
|
|
166
|
-
item.translations.set(loc, poi?.msgstr ?? []);
|
|
167
|
-
const add = {
|
|
168
|
-
comments: poi?.comments ?? [],
|
|
169
|
-
flags: {},
|
|
170
|
-
};
|
|
171
|
-
for (const [k, v] of Object.entries(poi?.flags ?? {})) {
|
|
172
|
-
if (!k.startsWith(urlAdapterFlagPrefix)) {
|
|
173
|
-
add.flags[k] = v;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
additionals.set(loc, add);
|
|
177
|
-
}
|
|
178
|
-
item.additionals = additionals;
|
|
179
|
-
items.push(item);
|
|
180
|
-
}
|
|
181
|
-
return { items, pluralRules };
|
|
179
|
+
return {
|
|
180
|
+
items: poitemsToItems(poItems.values(), this.opts.locales, this.opts.sourceLocale),
|
|
181
|
+
pluralRules,
|
|
182
|
+
};
|
|
182
183
|
}
|
|
183
184
|
async saveRaw(items, headers, locale, url) {
|
|
185
|
+
const filename = this.filesByLoc.get(locale)[Number(url)];
|
|
186
|
+
if (items.length === 0) {
|
|
187
|
+
if (await this.opts.fs.exists(filename)) {
|
|
188
|
+
await this.opts.fs.unlink(filename);
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
184
192
|
const po = new PO();
|
|
185
193
|
po.headers = headers;
|
|
186
194
|
po.items = items;
|
|
187
|
-
|
|
188
|
-
await
|
|
189
|
-
await new Promise((res, rej) => {
|
|
190
|
-
po.save(filename, err => {
|
|
191
|
-
if (err) {
|
|
192
|
-
rej(err);
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
res();
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
});
|
|
195
|
+
await this.opts.fs.mkdir(this.opts.dir);
|
|
196
|
+
await this.opts.fs.write(filename, po.toString());
|
|
199
197
|
}
|
|
200
198
|
async save(data) {
|
|
201
199
|
for (const locale of this.opts.locales) {
|
|
@@ -212,9 +210,7 @@ export class POFile {
|
|
|
212
210
|
}
|
|
213
211
|
const headers = this.getHeaders(locale, data.pluralRules.get(locale));
|
|
214
212
|
await this.saveRaw(poItems, headers, locale, false);
|
|
215
|
-
|
|
216
|
-
await this.saveRaw(poItemsUrl, headers, locale, true);
|
|
217
|
-
}
|
|
213
|
+
await this.saveRaw(poItemsUrl, headers, locale, true);
|
|
218
214
|
}
|
|
219
215
|
}
|
|
220
216
|
getHeaders(locale, pluralRule) {
|
package/dist/runtime.js
CHANGED
|
@@ -49,22 +49,17 @@ export default function toRuntime(mod = { [catalogVarName]: [] }, locale) {
|
|
|
49
49
|
/** for tagged template strings */
|
|
50
50
|
rt.t = (tag, id, args) => {
|
|
51
51
|
const ctx = getCompositeContext(id);
|
|
52
|
-
const strings = [];
|
|
52
|
+
const strings = [''];
|
|
53
53
|
const exprs = [];
|
|
54
|
-
if (typeof ctx[0] === 'number') {
|
|
55
|
-
strings.push('');
|
|
56
|
-
}
|
|
57
54
|
for (const x of ctx) {
|
|
58
55
|
if (typeof x === 'string') {
|
|
59
|
-
strings.
|
|
56
|
+
strings[strings.length - 1] += x;
|
|
60
57
|
continue;
|
|
61
58
|
}
|
|
62
59
|
exprs.push(args?.[x]);
|
|
63
|
-
}
|
|
64
|
-
if (typeof ctx.at(-1) === 'number') {
|
|
65
60
|
strings.push('');
|
|
66
61
|
}
|
|
67
|
-
return tag(strings, ...exprs);
|
|
62
|
+
return tag(Object.assign(strings, { raw: strings }), ...exprs);
|
|
68
63
|
};
|
|
69
64
|
/** get translation for plural */
|
|
70
65
|
rt.p = (id) => catalog[id] ?? [];
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FS } from './fs.js';
|
|
1
2
|
export type FileRefEntry = {
|
|
2
3
|
link?: string;
|
|
3
4
|
placeholders: [number, string][];
|
|
@@ -54,5 +55,6 @@ export type StorageFactoryOpts = {
|
|
|
54
55
|
/** whether the url is configured, can use to load separate url files */
|
|
55
56
|
haveUrl: boolean;
|
|
56
57
|
sourceLocale: string;
|
|
58
|
+
fs: FS;
|
|
57
59
|
};
|
|
58
60
|
export type StorageFactory = (opts: StorageFactoryOpts) => CatalogStorage;
|