wuchale 0.18.7 → 0.18.9

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.
@@ -34,7 +34,7 @@ export class MixedVisitor {
34
34
  scope: props.scope,
35
35
  element: props.element,
36
36
  attribute: props.attribute,
37
- }), null);
37
+ }));
38
38
  const heurMsgType = this.checkHeuristic(msg);
39
39
  if (heurMsgType) {
40
40
  let hasCompoundText = hasTextChild && hasNonTextChild;
@@ -8,5 +8,5 @@ export type VanillaArgs = AdapterArgs<LoadersAvailable>;
8
8
  export declare function getDefaultLoaderPath(loader: LoaderChoice<LoadersAvailable>, bundle: boolean): string | {
9
9
  client: string;
10
10
  server: string;
11
- };
12
- export declare const adapter: (args?: VanillaArgs) => Adapter;
11
+ } | null;
12
+ export declare const adapter: (args?: Partial<VanillaArgs>) => Adapter;
@@ -21,7 +21,7 @@ const defaultArgs = {
21
21
  runtime: {
22
22
  useReactive: ({ nested }) => ({
23
23
  init: nested ? null : false,
24
- use: nested ? null : false,
24
+ use: false,
25
25
  }),
26
26
  plain: {
27
27
  wrapInit: expr => expr,
@@ -6,7 +6,7 @@ import { type RuntimeVars, type CommentDirectives } from "../adapter-utils/index
6
6
  export declare const scriptParseOptions: Estree.Options;
7
7
  export declare function scriptParseOptionsWithComments(): [Estree.Options, Estree.Comment[][]];
8
8
  export declare function parseScript(content: string): [Estree.Program, Estree.Comment[][]];
9
- type InitRuntimeFunc = (file: string, funcName: string, parentFunc: string, additional: object) => string;
9
+ type InitRuntimeFunc = (file: string, funcName?: string, parentFunc?: string, additional?: object) => string | undefined;
10
10
  export declare class Transformer {
11
11
  index: IndexTracker;
12
12
  heuristic: HeuristicFunc;
@@ -21,19 +21,19 @@ export declare class Transformer {
21
21
  vars: () => RuntimeVars;
22
22
  commentDirectives: CommentDirectives;
23
23
  insideProgram: boolean;
24
- declaring: ScriptDeclType;
24
+ declaring?: ScriptDeclType;
25
25
  currentFuncNested: boolean;
26
- currentFuncDef: string | null;
26
+ currentFuncDef?: string;
27
27
  currentCall: string;
28
- currentTopLevelCall: string;
28
+ currentTopLevelCall?: string;
29
29
  /** .start of the first statements in their respective parents, to put the runtime init before */
30
30
  realBodyStarts: Set<number>;
31
31
  /** for subclasses. right now for svelte, if in <script module> */
32
32
  additionalState: object;
33
- constructor(content: string, filename: string, index: IndexTracker, heuristic: HeuristicFunc, patterns: CodePattern[], catalogExpr: CatalogExpr, rtConf: RuntimeConf, matchUrl: (url: string) => string, rtBaseVars?: string[]);
33
+ constructor(content: string, filename: string, index: IndexTracker, heuristic: HeuristicFunc, patterns: CodePattern[], catalogExpr: CatalogExpr, rtConf: RuntimeConf, matchUrl: UrlMatcher, rtBaseVars?: string[]);
34
34
  fullHeuristicDetails: (detailsBase: HeuristicDetailsBase) => HeuristicDetails;
35
35
  getHeuristicMessageType: (msg: Message) => HeuristicResultChecked;
36
- checkHeuristic: (msgStr: string, detailsBase: HeuristicDetailsBase) => [HeuristicResultChecked, Message];
36
+ checkHeuristic: (msgStr: string, detailsBase: HeuristicDetailsBase) => [MessageType, Message] | [false, null];
37
37
  visitLiteral: (node: Estree.Literal, heuristicDetailsBase?: HeuristicDetailsBase) => Message[];
38
38
  visitArrayExpression: (node: Estree.ArrayExpression) => Message[];
39
39
  visitSequenceExpression: (node: Estree.SequenceExpression) => Message[];
@@ -67,7 +67,7 @@ export declare class Transformer {
67
67
  visitExportDefaultDeclaration: (node: Estree.ExportNamedDeclaration) => Message[];
68
68
  visitStatementsNSaveRealBodyStart: (nodes: (Estree.Statement | Estree.ModuleDeclaration)[]) => Message[];
69
69
  getRealBodyStart: (nodes: (Estree.Statement | Estree.ModuleDeclaration)[]) => number | undefined;
70
- visitFunctionBody: (node: Estree.BlockStatement | Estree.Expression, name: string | null, end?: number) => Message[];
70
+ visitFunctionBody: (node: Estree.BlockStatement | Estree.Expression, name?: string, end?: number) => Message[];
71
71
  visitFunctionDeclaration: (node: Estree.FunctionDeclaration) => Message[];
72
72
  visitArrowFunctionExpression: (node: Estree.ArrowFunctionExpression) => Message[];
73
73
  visitFunctionExpression: (node: Estree.FunctionExpression) => Message[];
@@ -27,8 +27,8 @@ export function scriptParseOptionsWithComments() {
27
27
  accumulateComments.push({
28
28
  type: block ? 'Block' : 'Line',
29
29
  value: comment,
30
- start: null,
31
- end: null,
30
+ start: -1,
31
+ end: -1,
32
32
  });
33
33
  }
34
34
  },
@@ -55,9 +55,9 @@ export class Transformer {
55
55
  // state
56
56
  commentDirectives = {};
57
57
  insideProgram = false;
58
- declaring = null;
58
+ declaring;
59
59
  currentFuncNested = false;
60
- currentFuncDef = null;
60
+ currentFuncDef;
61
61
  currentCall;
62
62
  currentTopLevelCall;
63
63
  /** .start of the first statements in their respective parents, to put the runtime init before */
@@ -72,7 +72,6 @@ export class Transformer {
72
72
  this.filename = filename;
73
73
  this.matchUrl = matchUrl;
74
74
  const topLevelUseReactive = rtConf.useReactive({
75
- funcName: null,
76
75
  nested: false,
77
76
  file: filename,
78
77
  additional: this.additionalState,
@@ -97,7 +96,7 @@ export class Transformer {
97
96
  return useReactive.use ? currentVars.reactive : currentVars.plain;
98
97
  };
99
98
  this.initRuntime = (file, funcName, parentFunc, additional) => {
100
- const useReactive = rtConf.useReactive({ funcName, nested: parentFunc != null, file, additional });
99
+ const useReactive = rtConf.useReactive({ funcName, nested: parentFunc != null, file, additional: additional ?? {} });
101
100
  if (useReactive.init == null) {
102
101
  return;
103
102
  }
@@ -142,9 +141,10 @@ export class Transformer {
142
141
  }
143
142
  const msg = new Message(msgStr, this.fullHeuristicDetails(detailsBase), this.commentDirectives.context);
144
143
  const heuRes = this.getHeuristicMessageType(msg);
145
- if (heuRes) {
146
- msg.type = heuRes;
144
+ if (!heuRes) {
145
+ return [false, null];
147
146
  }
147
+ msg.type = heuRes;
148
148
  return [heuRes, msg];
149
149
  };
150
150
  visitLiteral = (node, heuristicDetailsBase) => {
@@ -159,7 +159,7 @@ export class Transformer {
159
159
  this.mstr.update(start, end, `${this.vars().rtTrans}(${this.index.get(msgInfo.toKey())})`);
160
160
  return [msgInfo];
161
161
  };
162
- visitArrayExpression = (node) => node.elements.map(this.visit).flat();
162
+ visitArrayExpression = (node) => node.elements.map(elm => elm ? this.visit(elm) : []).flat();
163
163
  visitSequenceExpression = (node) => node.expressions.map(this.visit).flat();
164
164
  visitObjectExpression = (node) => node.properties.map(this.visit).flat();
165
165
  visitObjectPattern = (node) => node.properties.map(this.visit).flat();
@@ -208,10 +208,10 @@ export class Transformer {
208
208
  const msgs = [];
209
209
  const updates = [];
210
210
  const appends = [];
211
- let lastArgEnd;
211
+ let lastArgEnd = null;
212
212
  for (const [i, arg] of pattern.args.entries()) {
213
213
  const argVal = node.arguments[i];
214
- let argInsertIndex;
214
+ let argInsertIndex = 0; // for now
215
215
  if (argVal == null) {
216
216
  argInsertIndex = lastArgEnd ?? node.callee.end + 1;
217
217
  if (lastArgEnd == null) {
@@ -271,7 +271,7 @@ export class Transformer {
271
271
  }
272
272
  const candidates = [];
273
273
  for (const elm of argVal.elements) {
274
- if (elm.type !== 'Literal' || typeof elm.value !== 'string') {
274
+ if (!elm || elm.type !== 'Literal' || typeof elm.value !== 'string') {
275
275
  return this.defaultVisitCallExpression(node);
276
276
  }
277
277
  candidates.push(elm.value);
@@ -385,8 +385,8 @@ export class Transformer {
385
385
  }
386
386
  const msgs = [...this.visit(node.id), ...this.visit(node.init)];
387
387
  if (atTopLevelDefn) {
388
- this.currentTopLevelCall = null; // reset
389
- this.declaring = null;
388
+ this.currentTopLevelCall = undefined; // reset
389
+ this.declaring = undefined;
390
390
  }
391
391
  return msgs;
392
392
  };
@@ -399,7 +399,7 @@ export class Transformer {
399
399
  const msgs = [];
400
400
  let bodyStart = null;
401
401
  for (const bod of nodes) {
402
- let currentContent;
402
+ let currentContent = ''; // for now
403
403
  if (bodyStart == null) {
404
404
  currentContent = this.mstr.toString();
405
405
  }
@@ -18,11 +18,11 @@ export type MessageType = 'message' | 'url';
18
18
  export declare class Message {
19
19
  msgStr: string[];
20
20
  plural: boolean;
21
- context: string;
21
+ context?: string;
22
22
  comments: string[];
23
23
  details: HeuristicDetails;
24
24
  type: MessageType;
25
- constructor(msgStr: string | string[], heuristicDetails: HeuristicDetails, context: string | null);
25
+ constructor(msgStr: string | (string | null | undefined)[], heuristicDetails?: HeuristicDetails, context?: string);
26
26
  toKey: () => string;
27
27
  }
28
28
  export type HeuristicResultChecked = MessageType | false;
@@ -33,7 +33,7 @@ export declare const defaultHeuristicOpts: {
33
33
  ignoreAttribs: string[][];
34
34
  ignoreCalls: string[];
35
35
  urlAttribs: string[][];
36
- urlCalls: any[];
36
+ urlCalls: string[];
37
37
  };
38
38
  export type CreateHeuristicOpts = typeof defaultHeuristicOpts;
39
39
  export declare function createHeuristic(opts: CreateHeuristicOpts): HeuristicFunc;
@@ -111,7 +111,7 @@ export type AdapterPassThruOpts = {
111
111
  outDir?: string;
112
112
  granularLoad: boolean;
113
113
  bundleLoad: boolean;
114
- url: {
114
+ url?: {
115
115
  patterns?: string[];
116
116
  localize?: boolean | URLLocalizer;
117
117
  };
@@ -132,9 +132,9 @@ export type CodePattern = {
132
132
  args: ('message' | 'pluralFunc' | 'locale' | 'other')[];
133
133
  };
134
134
  export type LoaderChoice<LoadersAvailable> = LoadersAvailable | string & {} | 'custom';
135
- export type AdapterArgs<LoadersAvailable> = Partial<AdapterPassThruOpts> & {
135
+ export type AdapterArgs<LoadersAvailable> = AdapterPassThruOpts & {
136
136
  loader: LoaderChoice<LoadersAvailable>;
137
- heuristic?: HeuristicFunc;
138
- patterns?: CodePattern[];
137
+ heuristic: HeuristicFunc;
138
+ patterns: CodePattern[];
139
139
  };
140
140
  export {};
package/dist/adapters.js CHANGED
@@ -1,3 +1,4 @@
1
+ const someHeuDet = { file: '', scope: 'markup' };
1
2
  export class Message {
2
3
  msgStr; // array for plurals
3
4
  plural = false;
@@ -5,7 +6,7 @@ export class Message {
5
6
  comments = [];
6
7
  details;
7
8
  type = 'message';
8
- constructor(msgStr, heuristicDetails, context) {
9
+ constructor(msgStr, heuristicDetails = someHeuDet, context) {
9
10
  if (typeof msgStr === 'string') {
10
11
  this.msgStr = [msgStr];
11
12
  }
@@ -14,7 +15,7 @@ export class Message {
14
15
  }
15
16
  this.msgStr = this.msgStr.map(msg => msg.split('\n').map(line => line.trim()).join('\n'));
16
17
  this.details = heuristicDetails;
17
- this.context = context ?? null;
18
+ this.context = context;
18
19
  }
19
20
  toKey = () => `${this.msgStr.slice(0, 2).join('\n')}\n${this.context ?? ''}`.trim();
20
21
  }
@@ -75,7 +76,7 @@ export function createHeuristic(opts) {
75
76
  if (msg.details.declaring === 'expression' && !msg.details.funcName) {
76
77
  return false;
77
78
  }
78
- if (!msg.details.call?.startsWith('console.') && !opts.ignoreCalls.includes(msg.details.call)) {
79
+ if (!msg.details.call || !msg.details.call.startsWith('console.') && !opts.ignoreCalls.includes(msg.details.call)) {
79
80
  return 'message';
80
81
  }
81
82
  return false;
@@ -5,6 +5,6 @@ type GeminiOpts = {
5
5
  think?: boolean;
6
6
  parallel?: number;
7
7
  };
8
- export declare function gemini({ apiKey, batchSize, think, parallel }?: GeminiOpts): AI;
9
- export declare const defaultGemini: AI;
8
+ export declare function gemini({ apiKey, batchSize, think, parallel }?: GeminiOpts): AI | null;
9
+ export declare const defaultGemini: AI | null;
10
10
  export {};
package/dist/ai/gemini.js CHANGED
@@ -15,7 +15,7 @@ function prepareData(content, instruction, think) {
15
15
  }
16
16
  export function gemini({ apiKey = 'env', batchSize = 50, think = false, parallel = 4 } = {}) {
17
17
  if (apiKey === 'env') {
18
- apiKey = process.env.GEMINI_API_KEY;
18
+ apiKey = process.env.GEMINI_API_KEY ?? '';
19
19
  }
20
20
  if (!apiKey) {
21
21
  return null;
@@ -35,7 +35,7 @@ export function gemini({ apiKey = 'env', batchSize = 50, think = false, parallel
35
35
  if (json.error) {
36
36
  throw new Error(`error: ${json.error.code} ${json.error.message}`);
37
37
  }
38
- return json.candidates[0]?.content.parts[0].text;
38
+ return json.candidates?.[0]?.content.parts[0].text ?? '';
39
39
  },
40
40
  };
41
41
  }
@@ -17,9 +17,12 @@ export async function extract(config, clean, watch, sync) {
17
17
  for (const [key, adapter] of Object.entries(config.adapters)) {
18
18
  const handler = new AdapterHandler(adapter, key, config, 'cli', process.cwd(), new Logger(config.logLevel));
19
19
  await handler.init(sharedState);
20
- await handler.directScanFS(clean, sync);
21
20
  handlers.push(handler);
22
21
  }
22
+ // other loop to make sure that all otherFileMatchers are collected
23
+ for (const handler of handlers) {
24
+ await handler.directScanFS(clean, sync);
25
+ }
23
26
  if (!watch) {
24
27
  console.info('Extraction finished.');
25
28
  return;
package/dist/cli/index.js CHANGED
@@ -12,13 +12,16 @@ const { positionals, values } = parseArgs({
12
12
  clean: {
13
13
  type: 'boolean',
14
14
  short: 'c',
15
+ default: false,
15
16
  },
16
17
  watch: {
17
18
  type: 'boolean',
18
19
  short: 'w',
20
+ default: false,
19
21
  },
20
22
  sync: {
21
23
  type: 'boolean',
24
+ default: false,
22
25
  },
23
26
  help: {
24
27
  type: 'boolean',
package/dist/config.d.ts CHANGED
@@ -2,18 +2,20 @@ import { type Adapter } from "./adapters.js";
2
2
  import type { AI } from "./ai/index.js";
3
3
  export type LogLevel = 'error' | 'warn' | 'info' | 'verbose';
4
4
  export type ConfigPartial = {
5
- sourceLocale?: string;
6
- otherLocales?: string[];
7
- ai?: AI;
8
- logLevel?: LogLevel;
5
+ sourceLocale: string;
6
+ otherLocales: string[];
7
+ ai: AI | null;
8
+ logLevel: LogLevel;
9
9
  };
10
10
  export type Config = ConfigPartial & {
11
- adapters?: Record<string, Adapter>;
12
- hmr?: boolean;
11
+ adapters: Record<string, Adapter>;
12
+ hmr: boolean;
13
13
  };
14
+ type ConfigWithOptional = Partial<Config>;
14
15
  export declare const defaultConfig: Config;
15
- export declare function defineConfig(config: Config): Config;
16
- export declare function deepMergeObjects<Type>(source: Type, target: Type): Type;
16
+ export declare function defineConfig(config: ConfigWithOptional): Partial<Config>;
17
+ export declare function deepMergeObjects<Type extends {}>(source: Partial<Type>, target: Type): Type;
17
18
  export declare const defaultConfigNames: string[];
18
19
  export declare const getLanguageName: (code: string) => string;
19
20
  export declare function getConfig(configPath?: string): Promise<Config>;
21
+ export {};
package/dist/config.js CHANGED
@@ -35,7 +35,7 @@ export function deepMergeObjects(source, target) {
35
35
  }
36
36
  export const defaultConfigNames = ['js', 'mjs'].map(ext => `wuchale.config.${ext}`);
37
37
  const displayName = new Intl.DisplayNames(['en'], { type: 'language' });
38
- export const getLanguageName = (code) => displayName.of(code);
38
+ export const getLanguageName = (code) => displayName.of(code) ?? code;
39
39
  function checkValidLocale(locale) {
40
40
  try {
41
41
  getLanguageName(locale);
@@ -45,17 +45,18 @@ function checkValidLocale(locale) {
45
45
  }
46
46
  }
47
47
  export async function getConfig(configPath) {
48
- let module;
48
+ let module = null;
49
49
  for (const confName of [configPath, ...defaultConfigNames]) {
50
50
  if (!confName) {
51
51
  continue;
52
52
  }
53
+ const fileUrl = `file://${resolve(confName)}`;
53
54
  try {
54
- module = await import(`file://${resolve(confName)}`);
55
+ module = await import(fileUrl);
55
56
  break;
56
57
  }
57
58
  catch (err) {
58
- if (err.code !== 'ERR_MODULE_NOT_FOUND') {
59
+ if (err.code !== 'ERR_MODULE_NOT_FOUND' || err.url != fileUrl) {
59
60
  throw err;
60
61
  }
61
62
  }
package/dist/handler.d.ts CHANGED
@@ -28,6 +28,7 @@ type Compiled = {
28
28
  type CompiledCatalogs = Record<string, Compiled>;
29
29
  type SharedState = {
30
30
  ownerKey: string;
31
+ otherFileMatches: Matcher[];
31
32
  poFilesByLoc: Record<string, POFile>;
32
33
  compiled: CompiledCatalogs;
33
34
  extractedUrls: Record<string, Catalog>;
@@ -61,6 +62,7 @@ export declare class AdapterHandler {
61
62
  getLoaderPath(): Promise<LoaderPath>;
62
63
  getCompiledFilePath(loc: string, id: string | null): string;
63
64
  getLoadIDs(forImport?: boolean): string[];
65
+ genProxy(catalogs: string[], loadIDs: string[], syncImports?: string[]): string;
64
66
  getProxy(): string;
65
67
  getProxySync(): string;
66
68
  getData(): string;
@@ -70,12 +72,12 @@ export declare class AdapterHandler {
70
72
  init: (sharedStates: SharedStates) => Promise<void>;
71
73
  urlPatternToTranslate: (pattern: string) => string;
72
74
  initUrlPatterns: () => Promise<void>;
73
- loadCatalogNCompile: (loc: string) => Promise<void>;
74
- loadCatalogModule: (locale: string, loadID: string, hmrVersion?: number) => string;
75
- matchUrl: (url: string) => string;
75
+ loadCatalogNCompile: (loc: string, hmrVersion?: number) => Promise<void>;
76
+ loadCatalogModule: (locale: string, loadID: string | null, hmrVersion: number) => string;
77
+ matchUrl: (url: string) => string | null;
76
78
  getUrlToCompile: (key: string, locale: string) => string;
77
- compile: (loc: string) => Promise<void>;
78
- writeCompiled: (loc: string) => Promise<void>;
79
+ compile: (loc: string, hmrVersion?: number) => Promise<void>;
80
+ writeCompiled: (loc: string, hmrVersion?: number) => Promise<void>;
79
81
  writeProxies: () => Promise<void>;
80
82
  writeTransformed: (filename: string, content: string) => Promise<void>;
81
83
  globConfToArgs: (conf: GlobConf) => [string[], {
package/dist/handler.js CHANGED
@@ -43,7 +43,7 @@ async function loadCatalogFromPO(filename) {
43
43
  const po = await loadPOFile(filename);
44
44
  const catalog = {};
45
45
  for (const item of po.items) {
46
- const msgInfo = new Message([item.msgid, item.msgid_plural], null, item.msgctxt);
46
+ const msgInfo = new Message([item.msgid, item.msgid_plural], undefined, item.msgctxt);
47
47
  catalog[msgInfo.toKey()] = item;
48
48
  }
49
49
  let pluralRule;
@@ -55,7 +55,12 @@ async function loadCatalogFromPO(filename) {
55
55
  else {
56
56
  pluralRule = defaultPluralRule;
57
57
  }
58
- return { catalog, pluralRule, headers: po.headers };
58
+ return {
59
+ catalog,
60
+ pluralRule,
61
+ // @ts-expect-error
62
+ headers: po.headers
63
+ };
59
64
  }
60
65
  function poDumpToString(items) {
61
66
  const po = new PO();
@@ -74,7 +79,7 @@ async function saveCatalogToPO(catalog, filename, headers = {}) {
74
79
  rej(err);
75
80
  }
76
81
  else {
77
- res(null);
82
+ res();
78
83
  }
79
84
  });
80
85
  });
@@ -208,6 +213,21 @@ export class AdapterHandler {
208
213
  }
209
214
  return loadIDs;
210
215
  }
216
+ // typed to work regardless of user's noUncheckedIndexedAccess setting in tsconfig
217
+ genProxy(catalogs, loadIDs, syncImports) {
218
+ const baseType = 'import("wuchale/runtime").CatalogModule';
219
+ return `
220
+ ${syncImports?.join('\n') ?? ''}
221
+ /** @typedef {${syncImports ? baseType : `() => Promise<${baseType}>`}} CatalogMod
222
+ /** @typedef {{[locale: string]: CatalogMod}} KeyCatalogs
223
+ /** @type {{[loadID: string]: KeyCatalogs}} */
224
+ const catalogs = {${catalogs.join(',')}}
225
+ export const loadCatalog = (/** @type {string} */ loadID, /** @type {string} */ locale) => {
226
+ return /** @type {CatalogMod} */ (/** @type {KeyCatalogs} */ (catalogs[loadID])[locale])${syncImports ? '' : '()'}
227
+ }
228
+ export const loadIDs = ['${loadIDs.join("', '")}']
229
+ `;
230
+ }
211
231
  getProxy() {
212
232
  const imports = [];
213
233
  const loadIDs = this.getLoadIDs();
@@ -219,12 +239,7 @@ export class AdapterHandler {
219
239
  }
220
240
  imports.push(`${id}: {${importsByLocale.join(',')}}`);
221
241
  }
222
- return `
223
- /** @type {{[loadID: string]: {[locale: string]: () => Promise<import('wuchale/runtime').CatalogModule>}}} */
224
- const catalogs = {${imports.join(',')}}
225
- export const loadCatalog = (/** @type {string} */ loadID, /** @type {string} */ locale) => catalogs[loadID][locale]()
226
- export const loadIDs = ['${loadIDs.join("', '")}']
227
- `;
242
+ return this.genProxy(imports, loadIDs);
228
243
  }
229
244
  getProxySync() {
230
245
  const loadIDs = this.getLoadIDs();
@@ -240,13 +255,7 @@ export class AdapterHandler {
240
255
  }
241
256
  object.push(`${id}: {${importedByLocale.join(',')}}`);
242
257
  }
243
- return `
244
- ${imports.join('\n')}
245
- /** @type {{[loadID: string]: {[locale: string]: import('wuchale/runtime').CatalogModule}}} */
246
- const catalogs = {${object.join(',')}}
247
- export const loadCatalog = (/** @type {string} */ loadID, /** @type {string} */ locale) => catalogs[loadID][locale]
248
- export const loadIDs = ['${loadIDs.join("', '")}']
249
- `;
258
+ return this.genProxy(object, loadIDs, imports);
250
259
  }
251
260
  getData() {
252
261
  return [
@@ -343,6 +352,7 @@ export class AdapterHandler {
343
352
  if (this.sharedState == null) {
344
353
  this.sharedState = {
345
354
  ownerKey: this.key,
355
+ otherFileMatches: [],
346
356
  poFilesByLoc: {},
347
357
  indexTracker: new IndexTracker(),
348
358
  compiled: {},
@@ -350,12 +360,15 @@ export class AdapterHandler {
350
360
  };
351
361
  sharedStates[this.#adapter.localesDir] = this.sharedState;
352
362
  }
363
+ else {
364
+ this.sharedState.otherFileMatches.push(this.fileMatches);
365
+ }
353
366
  this.catalogPathsToLocales = {};
354
367
  for (const loc of this.#locales) {
355
368
  this.#catalogsFname[loc] = this.catalogFileName(loc);
356
369
  // for handleHotUpdate
357
370
  this.catalogPathsToLocales[this.#catalogsFname[loc]] = loc;
358
- if (loc !== this.#config.sourceLocale) {
371
+ if (loc !== this.#config.sourceLocale && this.#config.ai) {
359
372
  this.#geminiQueue[loc] = new AIQueue(sourceLocaleName, getLanguageName(loc), this.#config.ai, async () => await this.savePoAndCompile(loc), this.#log);
360
373
  }
361
374
  if (this.sharedState.ownerKey === this.key) {
@@ -390,11 +403,11 @@ export class AdapterHandler {
390
403
  const urlPatternsForTranslate = urlPatterns.map(this.urlPatternToTranslate);
391
404
  const urlPatternMsgs = urlPatterns.map((patt, i) => {
392
405
  const locPattern = urlPatternsForTranslate[i];
393
- let context = null;
406
+ let context;
394
407
  if (locPattern !== patt) {
395
408
  context = `original: ${patt}`;
396
409
  }
397
- return new Message(locPattern, null, context);
410
+ return new Message(locPattern, undefined, context);
398
411
  });
399
412
  const urlPatternCatKeys = urlPatternMsgs.map(msg => msg.toKey());
400
413
  for (const [key, item] of Object.entries(catalog)) {
@@ -448,7 +461,7 @@ export class AdapterHandler {
448
461
  }
449
462
  await this.writeUrlFiles();
450
463
  };
451
- loadCatalogNCompile = async (loc) => {
464
+ loadCatalogNCompile = async (loc, hmrVersion = -1) => {
452
465
  if (this.sharedState.ownerKey === this.key) {
453
466
  try {
454
467
  this.sharedState.poFilesByLoc[loc] = await loadCatalogFromPO(this.#catalogsFname[loc]);
@@ -460,12 +473,12 @@ export class AdapterHandler {
460
473
  this.#log.warn(`${color.magenta(this.key)}: Catalog not found for ${color.cyan(loc)}`);
461
474
  }
462
475
  }
463
- await this.compile(loc);
476
+ await this.compile(loc, hmrVersion);
464
477
  };
465
- loadCatalogModule = (locale, loadID, hmrVersion = -1) => {
478
+ loadCatalogModule = (locale, loadID, hmrVersion) => {
466
479
  let compiledData = this.sharedState.compiled[locale];
467
480
  if (this.#adapter.granularLoad) {
468
- compiledData = this.granularStateByID[loadID]?.compiled?.[locale] ?? { hasPlurals: false, items: [] };
481
+ compiledData = loadID && this.granularStateByID[loadID]?.compiled?.[locale] || { hasPlurals: false, items: [] };
469
482
  }
470
483
  const compiledItems = JSON.stringify(compiledData.items);
471
484
  const plural = `n => ${this.sharedState.poFilesByLoc[locale].pluralRule.plural}`;
@@ -556,7 +569,7 @@ export class AdapterHandler {
556
569
  }
557
570
  return toCompile;
558
571
  };
559
- compile = async (loc) => {
572
+ compile = async (loc, hmrVersion = -1) => {
560
573
  this.sharedState.compiled[loc] ??= { hasPlurals: false, items: [] };
561
574
  const catalog = this.sharedState.poFilesByLoc[loc].catalog;
562
575
  for (const [key, poItem] of Object.entries({ ...catalog, ...this.sharedState.extractedUrls[loc] })) {
@@ -596,15 +609,15 @@ export class AdapterHandler {
596
609
  state.compiled[loc].items[state.indexTracker.get(key)] = compiled;
597
610
  }
598
611
  }
599
- await this.writeCompiled(loc);
612
+ await this.writeCompiled(loc, hmrVersion);
600
613
  };
601
- writeCompiled = async (loc) => {
602
- await writeFile(this.getCompiledFilePath(loc, null), this.loadCatalogModule(loc, null));
614
+ writeCompiled = async (loc, hmrVersion = -1) => {
615
+ await writeFile(this.getCompiledFilePath(loc, null), this.loadCatalogModule(loc, null, hmrVersion));
603
616
  if (!this.#adapter.granularLoad) {
604
617
  return;
605
618
  }
606
619
  for (const state of Object.values(this.granularStateByID)) {
607
- await writeFile(this.getCompiledFilePath(loc, state.id), this.loadCatalogModule(loc, state.id));
620
+ await writeFile(this.getCompiledFilePath(loc, state.id), this.loadCatalogModule(loc, state.id, hmrVersion));
608
621
  }
609
622
  };
610
623
  writeProxies = async () => {
@@ -760,7 +773,7 @@ export class AdapterHandler {
760
773
  if (!item.references.includes(filename)) {
761
774
  continue;
762
775
  }
763
- const key = new Message([item.msgid, item.msgid_plural], null, item.msgctxt).toKey();
776
+ const key = new Message([item.msgid, item.msgid_plural], undefined, item.msgctxt).toKey();
764
777
  previousReferences[key] = { count: 0, indices: [] };
765
778
  for (const [i, ref] of item.references.entries()) {
766
779
  if (ref !== filename) {
@@ -802,7 +815,7 @@ export class AdapterHandler {
802
815
  let iStartComm;
803
816
  if (key in previousReferences) {
804
817
  const prevRef = previousReferences[key];
805
- iStartComm = prevRef.indices.shift() * newComments.length; // cannot be pop for determinism
818
+ iStartComm = (prevRef.indices.shift() ?? 0) * newComments.length; // cannot be pop for determinism
806
819
  const prevComments = poItem.extractedComments.slice(iStartComm, iStartComm + newComments.length);
807
820
  if (prevComments.length !== newComments.length || prevComments.some((c, i) => c !== newComments[i])) {
808
821
  commentsChanged = true;
@@ -947,7 +960,15 @@ export class AdapterHandler {
947
960
  }
948
961
  else {
949
962
  // don't touch other adapters' files. related extracted comments handled by handler
950
- item.references = item.references.filter(ref => !this.fileMatches(ref));
963
+ item.references = item.references.filter(ref => {
964
+ if (this.fileMatches(ref)) {
965
+ return false;
966
+ }
967
+ if (this.sharedState.ownerKey !== this.key) {
968
+ return true;
969
+ }
970
+ return this.sharedState.otherFileMatches.some(match => match(ref));
971
+ });
951
972
  }
952
973
  }
953
974
  }
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  // by locale
4
4
  const runtimes = {};
5
5
  const runtimeCtx = new AsyncLocalStorage();
6
+ const emptyRuntime = toRuntime();
6
7
  let warningShown = {};
7
8
  export function currentRuntime(key, loadID) {
8
9
  const runtime = runtimeCtx.getStore()?.[key]?.[loadID];
@@ -11,10 +12,11 @@ export function currentRuntime(key, loadID) {
11
12
  }
12
13
  const warnKey = `${key}.${loadID}`;
13
14
  if (warningShown[warnKey]) {
14
- return;
15
+ return emptyRuntime;
15
16
  }
16
17
  console.warn(`Catalog for '${warnKey}' not found.\n Either 'runWithLocale' was not called or the environment has a problem.`);
17
18
  warningShown[warnKey] = true;
19
+ return emptyRuntime;
18
20
  }
19
21
  export async function loadLocales(key, loadIDs, load, locales) {
20
22
  if (loadIDs == null) {
package/dist/runtime.d.ts CHANGED
@@ -9,7 +9,7 @@ declare let onInvalidFunc: (i: number, c: CompiledElement[]) => string;
9
9
  export declare function onInvalid(newOnInvalid: typeof onInvalidFunc): void;
10
10
  export type Runtime = {
11
11
  _: CatalogModule;
12
- l: string;
12
+ l?: string;
13
13
  cx: (id: number) => Mixed | CompositePayload[];
14
14
  tx: (ctx: Mixed, args?: any[], start?: number) => string;
15
15
  tt: (tag: CallableFunction, id: number, args?: any[]) => any;
package/dist/url.d.ts CHANGED
@@ -7,11 +7,11 @@ type GetLocale = (url: URL, locales: string[]) => string | null;
7
7
  export type URLLocalizer = (url: string, locale: string) => string;
8
8
  export declare const localizeDefault: URLLocalizer;
9
9
  export declare const getLocaleDefault: GetLocale;
10
- type MatchParams = Record<string, string | string[]>;
10
+ type MatchParams = Partial<Record<string, string | string[]>>;
11
11
  export declare const fillParams: (params: MatchParams, destPattern: string) => string;
12
12
  type MatchResult = {
13
- path: string;
14
- locale: string;
13
+ path: string | null;
14
+ locale: string | null;
15
15
  params: MatchParams;
16
16
  altPatterns: Record<string, string>;
17
17
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wuchale",
3
- "version": "0.18.7",
3
+ "version": "0.18.9",
4
4
  "description": "Protobuf-like i18n from plain code",
5
5
  "scripts": {
6
6
  "dev": "tsc --watch",
@@ -87,7 +87,7 @@
87
87
  "tinyglobby": "^0.2.15"
88
88
  },
89
89
  "devDependencies": {
90
- "@types/node": "^24.10.1",
90
+ "@types/node": "~24.10.1",
91
91
  "@types/picomatch": "^4.0.1",
92
92
  "typescript": "^5.9.3"
93
93
  },